mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-10-05 06:05:28 +00:00
OpenErrand 0.1: an errand on a website with no API, with the human steps kept human (#227)
* OpenErrand 0.1: an errand on a website with no API, with the human steps kept human docs/openerrand.md mints OpenErrand: one JSON file per errand (register an account, download a transcript) naming the site, the inputs with a sensitivity class and ordered sources (document, vault, prompt, generate, derive, candidate, literal), field rules matched by id then label, page and wait steps, five human gates a runner never performs (declare, identity-proofing, code, mail, captcha), outcomes, the never-retried shared secret, vault and download outputs, hand-off cards that may name only public inputs, the publisher index at /.well-known/openerrand.json, and thirteen runner rules. The worked example is the MyFTB business registration that cli-tools `ftb` performs (profullstack/cli-tools#125), with no personal data. - @logicsrc/schemas: openerrand + openerrand-index schemas and fixtures - @logicsrc/validators: semantic checks (references, templates, no personal or secret input on a card) and tests that validate the spec's own examples - logicsrc-web: registry entry (process family), /openerrand landing page, the example and the index served as static files, contract tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * OpenErrand: hand-off cards stay on the surface that owns the data Anthony's ruling: tax and finance data never touches a social or promotion tool, and nothing is sent to a CPA or preparer. - Hand-off cards are delivered only on the surface that owns the errand's data (for a tax or finance errand, the principal's finance app through its CLI, PWA, MCP server or API, such as CoinPay, or the runner's terminal), never a social, promotion or third-party posting service, and never to anyone but the principal. A card for an errand with personal or secret inputs does not leave that surface. Runner rule 9 says the same. - The run record and the sample run name the card by an opaque id (pin-letter/7f3k2q) instead of a mynaposter.com URL; the myna mention is gone. - `principal: represented` no longer cites a preparer with a power of attorney. - The FTB card's last step no longer suggests sending the PIN to someone else. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * OpenErrand: user-agent rule, captcha solver policy, reference runner note Anthony's answers on #227 ("go with your recommendations"): - Rule 11: a runner may run headless with a normal desktop browser user agent (dropping HeadlessChrome) and nothing more: no fingerprint spoofing beyond the UA string, no stealth plugins, no solving or evading a bot challenge. A challenge the browser completes itself is a wait step; any other is a captcha gate. - Captcha solvers: new site.sector and captcha step `solver` (forbidden by default | allowed). Never allowed on government, tax, financial, healthcare or identity-provider sites, nor on any errand with a declare or identity-proofing step or a secret input; elsewhere only when the file says so, with every use logged. The validator rejects `allowed` in the forbidden set or without a stated sector; six new tests. The FTB example states sector "tax". - Reference runner: @logicsrc/openerrand / `logicsrc errand run`, marked in progress; ftb stays the runner the example was taken from. - Name stays OpenErrand; family stays Agents and process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d38db62e8b
commit
1d69dc3804
19 changed files with 2231 additions and 3 deletions
31
apps/logicsrc-web/src/app/openerrand/data.ts
Normal file
31
apps/logicsrc-web/src/app/openerrand/data.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// The facts the /openerrand landing page shows, kept apart from page.tsx so
|
||||
// contract/openerrand.contract.test.ts can hold them against docs/openerrand.md.
|
||||
|
||||
/** The step kinds, in the order the spec's Steps table lists them. */
|
||||
export const STEPS: Array<[string, string]> = [
|
||||
["page", "A form page: fill it from the rules, then press its forward button."],
|
||||
["wait", "An interstitial the page clears by itself, such as a proof-of-work bot check. Waited out, never solved or bypassed."],
|
||||
["declare", "A legal attestation. Ticked only on the principal's consent for this run, given after seeing the values."],
|
||||
["identity-proofing", "A selfie, a video call or an ID scan at a provider such as ID.me. Never driven by the runner: the person does it in a visible window."],
|
||||
["code", "A one-time code by text, email, call or app, relayed by the person through a terminal, a file or the runner's own page."],
|
||||
["mail", "A letter with a PIN. The run ends as waiting and a hand-off card says what to do when it comes."],
|
||||
["captcha", "Shown to the principal, or the run stops. A solver only where the errand says so, never on a government, tax, financial, healthcare or identity-provider site, never with a declaration, identity proofing or a secret input."]
|
||||
];
|
||||
|
||||
/** The sensitivity classes, in the order the spec lists them. */
|
||||
export const SENSITIVITY: Array<[string, string]> = [
|
||||
["public", "Logs, the terminal, hand-off cards, the run record."],
|
||||
["personal", "The principal's terminal, the site's own fields, the vault. Never a log, never a card."],
|
||||
["secret", "The site's own field and the vault, and nowhere else. Masked as •••• everywhere."]
|
||||
];
|
||||
|
||||
/** Where an input may come from, in the order the spec lists them. */
|
||||
export const SOURCES: Array<[string, string]> = [
|
||||
["document", "Extracted on the principal's machine from their own files, with the file and page recorded. The document never leaves the machine."],
|
||||
["vault", "A key in the principal's vault, read before anything is generated, so a second run reuses the first run's login."],
|
||||
["prompt", "Asked of a person at run time. A secret prompt does not echo."],
|
||||
["generate", "A fresh random value, written to the vault on success."],
|
||||
["derive", "Another input, transformed: the digits of a street address."],
|
||||
["candidate", "A part of the chosen shared-secret candidate, such as the tax year the figure came from."],
|
||||
["literal", "A fixed value."]
|
||||
];
|
||||
198
apps/logicsrc-web/src/app/openerrand/page.tsx
Normal file
198
apps/logicsrc-web/src/app/openerrand/page.tsx
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { specMetadata } from "@/lib/page-meta";
|
||||
import { SiteShell } from "@/components/site-shell";
|
||||
import { mono, pre, table, td, th } from "../openontology/ui";
|
||||
import { SENSITIVITY, SOURCES, STEPS } from "./data";
|
||||
|
||||
export const metadata: Metadata = specMetadata(
|
||||
"/openerrand",
|
||||
"OpenErrand is one JSON file that describes an errand on a website with no API: the inputs and where each comes from, the rules that fill each field, the human gates a runner must never automate (declarations, identity proofing, codes, letters, captchas), the outcomes, and a hand-off card that never carries a secret."
|
||||
);
|
||||
|
||||
const EXCERPT = `"rules": [
|
||||
{ "name": "first name", "id": "^FstName$", "do": { "text": "{{first_name}}" } },
|
||||
{ "name": "net income", "label": "net income|income \\\\(loss\\\\)",
|
||||
"types": ["text", "tel", "number"], "do": { "text": "{{net_income}}" } },
|
||||
{ "name": "declaration", "label": "perjury|i declare|under penalty",
|
||||
"types": ["checkbox"], "do": { "gate": "declaration" } }
|
||||
],
|
||||
"steps": [
|
||||
{ "id": "declaration", "kind": "declare",
|
||||
"statement": "perjury|i declare|under penalty",
|
||||
"why": "Ticking this box is the representative stating, under penalty of perjury, that what was entered is true." }
|
||||
]`;
|
||||
|
||||
const RULES: Array<[string, string]> = [
|
||||
["Shows the errand first", "Title, publisher, every gate with its reason, every input with its sensitivity and source. A file whose hash changed is shown again before it runs."],
|
||||
["Never guesses", "Fields are matched by id first and label second. A required field no rule fills stops the run."],
|
||||
["Never performs a gate", "Declarations need the person's consent for this run. Identity proofing and letters are theirs, and so are captchas on any sensitive site. Codes come only through a declared relay."],
|
||||
["One shared secret per run", "A figure from a return is submitted once. A rejection ends the run and lists the other candidates; a person picks the next one."],
|
||||
["Keeps documents and values in place", "Extraction is local. Values go to the site's fields and the vault; logs hold fields, never values; cards hold steps, never secrets."]
|
||||
];
|
||||
|
||||
export default function OpenErrandPage(): ReactNode {
|
||||
return (
|
||||
<SiteShell active="OpenErrand">
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<p className="eyebrow">LogicSRC standards surface</p>
|
||||
<h2>OpenErrand</h2>
|
||||
<p>
|
||||
One JSON file that describes an errand a person runs on a website that has no API:
|
||||
registering for a tax account, downloading a transcript, renewing a licence. A person
|
||||
reads it and knows every value it will send and every statement it will ask them to
|
||||
make. An agent runs the same file in a headless browser and stops exactly where a
|
||||
person is needed.
|
||||
</p>
|
||||
</div>
|
||||
<p style={{ color: "#41505d" }}>
|
||||
People already automate these sites, with a script nobody else can read or an agent
|
||||
left to guess. The script hides what it sends. The agent ticks a penalty-of-perjury box
|
||||
because the form would not submit without it, and tries a second figure when the first
|
||||
is rejected. OpenErrand writes the errand down: the fields it fills and with what, the
|
||||
values that are secret, the steps that belong to a person, and the point where it stops.
|
||||
</p>
|
||||
<p style={{ color: "#5b6b7a" }}>
|
||||
Status: 0.1. A generic runner, <code style={mono}>logicsrc errand run</code> from{" "}
|
||||
<code style={mono}>@logicsrc/openerrand</code>, is in progress. The worked example
|
||||
transcribes the rule table of <code style={mono}>ftb</code> in cli-tools, which registers
|
||||
and activates MyFTB accounts at the California Franchise Tax Board. An excerpt:
|
||||
</p>
|
||||
<pre style={pre}>{EXCERPT}</pre>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>Steps and human gates</h2>
|
||||
<p>
|
||||
Five of the seven step kinds are gates: steps only a person can take. A runner hands
|
||||
them over and never performs them, and each carries a <code style={mono}>why</code> a
|
||||
person reads.
|
||||
</p>
|
||||
</div>
|
||||
<table style={table}>
|
||||
<tbody>
|
||||
{STEPS.map(([kind, what]) => (
|
||||
<tr key={kind}>
|
||||
<td style={td}>
|
||||
<code style={mono}>{kind}</code>
|
||||
</td>
|
||||
<td style={td}>{what}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>Inputs</h2>
|
||||
<p>Every input has a sensitivity class, which decides where its value may appear.</p>
|
||||
</div>
|
||||
<table style={table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={th}>class</th>
|
||||
<th style={th}>where it may appear</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{SENSITIVITY.map(([name, where]) => (
|
||||
<tr key={name}>
|
||||
<td style={td}>
|
||||
<code style={mono}>{name}</code>
|
||||
</td>
|
||||
<td style={td}>{where}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<p style={{ color: "#41505d" }}>And an ordered list of sources, tried until one yields a value:</p>
|
||||
<table style={table}>
|
||||
<tbody>
|
||||
{SOURCES.map(([name, what]) => (
|
||||
<tr key={name}>
|
||||
<td style={td}>
|
||||
<code style={mono}>{name}</code>
|
||||
</td>
|
||||
<td style={td}>{what}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>What a runner promises</h2>
|
||||
<p>Thirteen rules in the specification; these are the ones that keep a person safe.</p>
|
||||
</div>
|
||||
<table style={table}>
|
||||
<tbody>
|
||||
{RULES.map(([rule, meaning]) => (
|
||||
<tr key={rule}>
|
||||
<td style={td}>
|
||||
<strong>{rule}</strong>
|
||||
</td>
|
||||
<td style={td}>{meaning}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>Discovery</h2>
|
||||
<p>
|
||||
A publisher lists its errands at <code style={mono}>/.well-known/openerrand.json</code>,
|
||||
each with the gates it will ask of a person. An errand is verified when it comes from
|
||||
that publisher's origin, and site-endorsed only when the site itself serves the
|
||||
index. This site publishes the worked example at{" "}
|
||||
<a href="/.well-known/openerrand.json">
|
||||
<code style={mono}>/.well-known/openerrand.json</code>
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>Not</h2>
|
||||
</div>
|
||||
<p style={{ color: "#41505d" }}>
|
||||
Not a way around a check: a runner may present a normal browser user agent and nothing more, with no fingerprint spoofing, no stealth plugins and no challenge solving.
|
||||
Not for someone else's account. Not an API: when a site has one, use{" "}
|
||||
<Link href="/openconnection">OpenConnection</Link>,{" "}
|
||||
<Link href="/openaccess">OpenAccess</Link> or <Link href="/opensaas">OpenSaaS</Link>. Not a
|
||||
scraper, not a test framework, not a credential store, not legal or tax advice.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="band">
|
||||
<div className="section-head">
|
||||
<h2>Where everything lives</h2>
|
||||
</div>
|
||||
<ul style={{ color: "#41505d", lineHeight: 1.9, paddingLeft: "1.1rem" }}>
|
||||
<li>
|
||||
<Link href="/docs/openerrand">Specification</Link>: the file, inputs, field rules,
|
||||
steps, the five gates, outcomes and retry, outputs, hand-off cards, discovery, thirteen
|
||||
runner rules, and the MyFTB business registration as a worked example
|
||||
</li>
|
||||
<li>
|
||||
JSON Schemas <code style={mono}>@logicsrc/schemas/openerrand</code> and{" "}
|
||||
<code style={mono}>openerrand-index</code>, checked by{" "}
|
||||
<code style={mono}>@logicsrc/validators</code>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/opencreds">OpenCreds</Link>, the vault an errand writes logins to;{" "}
|
||||
<Link href="/openfleet">OpenFleet</Link>, the record an agent runs an errand under
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ export const FAMILIES: Family[] = [
|
|||
name: "Agents and process",
|
||||
line: "How agents coordinate, settle, stream, and how the software that serves them gets built",
|
||||
blurb:
|
||||
"The lifecycle for building software when agents work in parallel and CI is the only gate, the requirement document an agent can execute, the settlement and proof layer under a peer-to-peer swarm, a lossless byte-stream envelope, the five nouns a shared ontology needs, the record an agent session carries about who spawned it and under what ceiling, and the one script that puts an application into service on the box it runs on.",
|
||||
"The lifecycle for building software when agents work in parallel and CI is the only gate, the requirement document an agent can execute, the settlement and proof layer under a peer-to-peer swarm, a lossless byte-stream envelope, the five nouns a shared ontology needs, the record an agent session carries about who spawned it and under what ceiling, the one script that puts an application into service on the box it runs on, and the errand file an agent runs on a website with no API while handing every legal, identity and mail step to a person.",
|
||||
specs: [
|
||||
s("asdlc", "ASDLC", "The Agentic Software Development Lifecycle: nine phases, four conformance levels and the ratchet rule"),
|
||||
s("openabtest", "OpenABTest", "Portable experiments with sticky assignments, distinct exposure and conversion events, and reconciled profit accounting", { landing: undefined, status: "draft" }),
|
||||
|
|
@ -128,6 +128,7 @@ export const FAMILIES: Family[] = [
|
|||
s("openontology", "OpenOntology", "Five nouns for a shared ontology, with governance and interoperability notes"),
|
||||
s("openfleet", "OpenFleet", "Agents under a human: the record a session carries about who spawned it, for what and under what ceiling, and the ledger its sysop reads"),
|
||||
s("openinstall", "OpenInstall", "One idempotent bin/install.sh in every repository that puts the app into service on the box it runs on: runtime, database, build, systemd, nginx and TLS, a health check, and exit codes a deployer can roll back on"),
|
||||
s("openerrand", "OpenErrand", "One JSON file for an errand on a website with no API: inputs and where each comes from, field rules matched by id then label, human gates a runner never automates, outcomes, a never-retried shared secret, and a hand-off card with no secrets"),
|
||||
s("openspec", "OpenSpec.dev comparison", "How LogicSRC compares with OpenSpec.dev, and the compatibility mode", { doc: "/docs/openspec-comparison" })
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue