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 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>
66 lines
3.1 KiB
TypeScript
66 lines
3.1 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { readDoc } from "../src/lib/docs";
|
|
import { SENSITIVITY, SOURCES, STEPS } from "../src/app/openerrand/data";
|
|
|
|
// The landing page restates the spec's tables, and the site serves the worked
|
|
// example and the publisher index. These tests keep all of them in step with
|
|
// docs/openerrand.md and the schema fixtures.
|
|
|
|
const doc = readDoc("openerrand") ?? "";
|
|
const root = resolve(__dirname, "../../..");
|
|
const json = (path: string) => JSON.parse(readFileSync(resolve(root, path), "utf8"));
|
|
|
|
/** The first-column `code` cells of the table under a heading. */
|
|
function tableKeys(heading: string): string[] {
|
|
const start = doc.indexOf(`\n${heading}\n`);
|
|
expect(start, `docs/openerrand.md has a ${heading} section`).toBeGreaterThan(-1);
|
|
const next = doc.slice(start + 1).search(/\n#{2,3} /);
|
|
const section = doc.slice(start, next === -1 ? undefined : start + 1 + next);
|
|
return section
|
|
.split("\n")
|
|
.map((line) => line.match(/^\| `([^`]+)` \|/))
|
|
.filter((m): m is RegExpMatchArray => m !== null)
|
|
.map((m) => m[1]);
|
|
}
|
|
|
|
describe("OpenErrand: the spec, its landing page and its published files agree", () => {
|
|
it("lists the same step kinds, sensitivity classes and sources, in the same order", () => {
|
|
expect(STEPS.map(([kind]) => kind)).toEqual(tableKeys("## Steps"));
|
|
expect(SENSITIVITY.map(([name]) => name)).toEqual(tableKeys("### Sensitivity"));
|
|
expect(SOURCES.map(([name]) => name)).toEqual(tableKeys("### Sources"));
|
|
});
|
|
|
|
it("gives every gate kind its own section", () => {
|
|
for (const gate of ["declare", "identity-proofing", "code", "mail", "captcha"]) {
|
|
expect(doc).toContain(`\n### \`${gate}\`\n`);
|
|
}
|
|
});
|
|
|
|
it("prints the fixture as the worked example, and the site serves the same bytes", () => {
|
|
const fixture = json("packages/schemas/fixtures/openerrand/ftb-register-business.json");
|
|
const section = doc.slice(doc.indexOf("\n## Worked example\n"));
|
|
const example = section.match(/```json\n([\s\S]*?)\n```/);
|
|
expect(example, "the worked example's JSON").not.toBeNull();
|
|
expect(JSON.parse(example![1])).toEqual(fixture);
|
|
expect(json("apps/logicsrc-web/public/examples/openerrand/ftb-register-business.json")).toEqual(fixture);
|
|
expect(fixture.id).toBe("https://logicsrc.com/examples/openerrand/ftb-register-business.json");
|
|
});
|
|
|
|
it("serves the publisher index that lists the worked example", () => {
|
|
const index = json("apps/logicsrc-web/public/.well-known/openerrand.json");
|
|
expect(index).toEqual(json("packages/schemas/fixtures/openerrand/index.json"));
|
|
expect(index.errands.map((e: { url: string }) => e.url)).toContain("https://logicsrc.com/examples/openerrand/ftb-register-business.json");
|
|
});
|
|
|
|
it("numbers its runner rules without gaps", () => {
|
|
const numbers = [...doc.matchAll(/^### (\d+)\. /gm)].map((m) => Number(m[1]));
|
|
expect(numbers.length).toBe(13);
|
|
expect(numbers).toEqual(numbers.map((_, i) => i + 1));
|
|
});
|
|
|
|
it("has no em dashes", () => {
|
|
expect(doc).not.toContain(String.fromCharCode(0x2014));
|
|
});
|
|
});
|