Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions apps/logicsrc-web/contract/openerrand.contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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));
});
});
3 changes: 2 additions & 1 deletion apps/logicsrc-web/contract/spec-discovery.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ describe.each([
{ slug: "openobject", name: "OpenObject", family: "catalogs" },
{ slug: "openslice", name: "OpenSlice", family: "catalogs" },
{ slug: "openstack", name: "OpenStack.md", family: "catalogs" },
{ slug: "openinstall", name: "OpenInstall", family: "process" }
{ slug: "openinstall", name: "OpenInstall", family: "process" },
{ slug: "openerrand", name: "OpenErrand", family: "process" }
])("$name public discovery", ({ slug, name, family }) => {
it("serves the specification through its family and docs index", () => {
expect(familyOfSpec(slug)?.slug).toBe(family);
Expand Down
16 changes: 16 additions & 0 deletions apps/logicsrc-web/public/.well-known/openerrand.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"type": "logicsrc.openerrand-index",
"version": "0.1",
"publisher": "https://logicsrc.com/.well-known/openprofile.md",
"updated": "2026-10-04T00:00:00Z",
"errands": [
{
"url": "https://logicsrc.com/examples/openerrand/ftb-register-business.json",
"name": "ftb-register-business",
"site": "https://webapp.ftb.ca.gov",
"title": "Register a MyFTB business account",
"gates": ["declare", "code", "mail"],
"updated": "2026-10-04T00:00:00Z"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
{
"type": "logicsrc.openerrand",
"version": "0.1",
"id": "https://logicsrc.com/examples/openerrand/ftb-register-business.json",
"name": "ftb-register-business",
"title": "Register a MyFTB business account",
"description": "Creates a MyFTB account for a California S corporation at the Franchise Tax Board, proving the business with a figure from a filed Form 100S. FTB then mails a PIN; activation is a second errand.",
"publisher": "https://logicsrc.com/.well-known/openprofile.md",
"updated": "2026-10-04T00:00:00Z",
"reference": "https://github.com/profullstack/cli-tools/pull/125",
"principal": "self",
"site": {
"name": "California Franchise Tax Board (MyFTB)",
"sector": "tax",
"origins": ["https://webapp.ftb.ca.gov"],
"start": ["https://webapp.ftb.ca.gov/MyFTBAccess/Registration/NewAccount"]
},
"limits": { "pages": 15, "page_timeout": "PT30S", "same_page": 2 },
"inputs": {
"email": {
"label": "Email address FTB writes to",
"type": "email",
"sensitivity": "personal",
"required": true,
"sources": [{ "from": "prompt" }]
},
"phone": {
"label": "Mobile number FTB texts a verification code to",
"type": "string",
"pattern": "^[0-9]{10}$",
"sensitivity": "personal",
"required": true,
"sources": [{ "from": "prompt" }]
},
"first_name": {
"label": "Representative's first name",
"type": "string",
"max_length": 11,
"sensitivity": "personal",
"sources": [{ "from": "document", "form": "CA 540", "field": "first name", "pick": "newest" }]
},
"last_name": {
"label": "Representative's last name",
"type": "string",
"max_length": 13,
"sensitivity": "personal",
"sources": [{ "from": "document", "form": "CA 540", "field": "last name", "pick": "newest" }]
},
"street": {
"label": "Street address on the newest return",
"type": "string",
"sensitivity": "personal",
"sources": [{ "from": "document", "form": "CA 540", "field": "street address", "pick": "newest" }]
},
"address_numbers": {
"label": "The numbers in the address on file",
"type": "string",
"sensitivity": "personal",
"sources": [{ "from": "derive", "input": "street", "transform": "digits" }]
},
"zip": {
"label": "ZIP code on file",
"type": "string",
"sensitivity": "personal",
"sources": [{ "from": "document", "form": "CA 540", "field": "ZIP code", "pick": "newest", "transform": "first:5" }]
},
"corp_id": {
"label": "California corporation number",
"type": "string",
"sensitivity": "public",
"sources": [{ "from": "document", "form": "CA 100S", "field": "California corporation number", "pick": "newest" }]
},
"tax_year": {
"label": "Tax year of the return the shared secret comes from",
"type": "integer",
"sensitivity": "public",
"sources": [{ "from": "candidate", "input": "net_income", "part": "year" }]
},
"net_income": {
"label": "Net income for tax purposes, whole dollars",
"type": "integer",
"sensitivity": "secret",
"role": "shared-secret",
"sources": [
{ "from": "document", "form": "CA 100S", "field": "line 20", "match": "net income for tax purposes", "years": { "back": 5, "current": false }, "transform": "whole" },
{ "from": "document", "form": "CA 100S", "field": "line 15", "match": "net income \\(loss\\) for state purposes", "years": { "back": 5, "current": false }, "transform": "whole" }
]
},
"username": {
"label": "MyFTB user name",
"type": "string",
"sensitivity": "personal",
"role": "credential",
"sources": [
{ "from": "vault", "key": "FTB_BUSINESS_USERNAME" },
{ "from": "generate", "length": 15, "classes": ["lower", "digit"] }
]
},
"password": {
"label": "MyFTB password",
"type": "string",
"sensitivity": "secret",
"role": "credential",
"sources": [
{ "from": "vault", "key": "FTB_BUSINESS_PASSWORD" },
{ "from": "generate", "length": 24, "classes": ["lower", "upper", "digit", "special"], "special": "!#$*@" }
]
},
"security": {
"label": "Three security questions and their answers",
"type": "qa-set",
"count": 3,
"sensitivity": "secret",
"role": "credential",
"sources": [
{ "from": "vault", "key": "FTB_BUSINESS_SECURITY_ANSWERS" },
{ "from": "generate", "length": 10, "classes": ["lower", "digit"] }
]
}
},
"rules": [
{ "name": "read terms", "id": "^ReadTerms$", "types": ["checkbox"], "do": { "check": true } },
{ "name": "accept terms", "id": "^AcceptTerms$", "types": ["checkbox"], "do": { "check": true } },
{ "name": "first name", "id": "^FstName$", "do": { "text": "{{first_name}}" } },
{ "name": "middle initial", "id": "^MInitial$", "do": { "skip": true } },
{ "name": "last name", "id": "^LstName$", "do": { "text": "{{last_name}}" } },
{ "name": "suffix", "id": "^Sffx$", "do": { "skip": true } },
{ "name": "user name again", "id": "^ReUserName$", "do": { "text": "{{username}}" } },
{ "name": "user name", "id": "^UserName$", "do": { "text": "{{username}}" } },
{ "name": "email again", "id": "^ReEmail$", "do": { "text": "{{email}}" } },
{ "name": "email", "id": "^Email$", "do": { "text": "{{email}}" } },
{ "name": "password again", "id": "^RePassword$", "do": { "text": "{{password}}" } },
{ "name": "password", "id": "^Password$", "do": { "text": "{{password}}" } },
{ "name": "foreign number", "id": "^Phone_Foreign$", "do": { "skip": true } },
{ "name": "foreign address", "id": "^Address_Foreign$|^Address_No(MailAddress|PostalCode)$", "do": { "skip": true } },
{ "name": "security question", "label": "question", "types": ["select-one"], "do": { "choose": "security" } },
{ "name": "security answer", "label": "question|answer", "types": ["text", "password"], "do": { "answer": "security" } },
{ "name": "role", "label": "individual|business representative", "types": ["radio"], "do": { "check": { "label": "^\\s*business representative" } } },
{ "name": "zip", "label": "zip|postal", "types": ["text", "tel", "number"], "do": { "text": "{{zip}}" } },
{ "name": "address numbers", "label": "numbers in (the |your )?(business )?(mailing )?address", "types": ["text", "tel", "number"], "do": { "text": "{{address_numbers}}" } },
{ "name": "tax year", "label": "year (of|on) the tax return|tax year", "types": ["select-one"], "do": { "select": ["^\\s*{{tax_year}}\\s*$"] } },
{ "name": "tax year", "label": "year (of|on) the tax return|tax year", "types": ["text", "tel", "number"], "do": { "text": "{{tax_year}}" } },
{ "name": "net income", "label": "net income|income \\(loss\\)", "types": ["text", "tel", "number"], "do": { "text": "{{net_income}}" } },
{ "name": "company type", "label": "type of company|company type|entity type", "types": ["select-one"], "do": { "select": ["^\\s*corporation\\s*$", "corporation"] } },
{ "name": "account number", "label": "account number|entity id|corporation (id|number)", "types": ["text", "tel", "number"], "do": { "text": "{{corp_id}}" } },
{ "name": "form type", "label": "form type|type of (tax )?(return|form)", "types": ["select-one"], "do": { "select": ["100\\s*S\\b"] } },
{ "name": "declaration", "label": "perjury|i declare|under penalty", "types": ["checkbox"], "do": { "gate": "declaration" } },
{ "name": "phone", "label": "phone number", "types": ["text", "tel", "number"], "do": { "text": "{{phone}}" } },
{ "name": "send a text", "label": "send me a text|text message", "types": ["radio"], "do": { "check": true } },
{ "name": "verification code", "label": "verification code|security code|one[- ]time|passcode|access code|enter (the )?code", "types": ["text", "tel", "number", "password"], "do": { "gate": "text-code" } }
],
"steps": [
{
"id": "bot-check",
"kind": "wait",
"match": { "title": "^Challenge Validation$", "selector": "#sec-cpt-if" },
"timeout": "PT90S",
"poll": "PT3S",
"say": "FTB's bot check is a proof of work the page's own script solves; the runner waits for it."
},
{
"id": "form",
"kind": "page",
"unmatched": "stop",
"say": "Every MyFTB registration page: terms, profile, security questions, role, address, shared secret, phone."
},
{
"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. Only that person can make the statement."
},
{
"id": "text-code",
"kind": "code",
"channel": "sms",
"relay": ["terminal", "file"],
"pattern": "^\\w{4,10}$",
"timeout": "PT15M",
"why": "FTB texts a code to the phone number given. Whoever holds the phone reads it out."
},
{
"id": "pin-letter",
"kind": "mail",
"what": "MyFTB PIN letter",
"arrives": "5 to 10 business days, to the address FTB has on file",
"expires": "P21D",
"resume": "ftb-activate-business",
"input": "pin",
"handoff": "pin-letter",
"why": "FTB activates a new account with a PIN it sends by US Mail. Nobody but the addressee can read it."
}
],
"submit": {
"labels": "^(submit|continue|next|log ?in|login|activate|send( code| me a code)?|verify|confirm)$",
"never": "^(back|cancel|end session|previous)$",
"ignore": "#timer, .modal"
},
"outcomes": [
{
"name": "rejected",
"kind": "rejected",
"text": "does not match our records|there is a problem|unable to (verify|process) your|account (is|has been) locked"
},
{
"name": "registered",
"kind": "success",
"text": "registration confirmation|successfully (registered|created)|we will (mail|send) you a (letter|pin)|pin .*(mail|letter)",
"then": "pin-letter"
}
],
"retry": { "shared_secret": "never", "page_errors": "rejected" },
"outputs": {
"vault": {
"when": "registered",
"keys": {
"FTB_BUSINESS_USERNAME": "{{username}}",
"FTB_BUSINESS_PASSWORD": "{{password}}",
"FTB_BUSINESS_EMAIL": "{{email}}",
"FTB_BUSINESS_SECURITY_ANSWERS": "{{security}}"
}
}
},
"handoffs": {
"pin-letter": {
"title": "FTB PIN letter: business MyFTB account",
"open": "https://webapp.ftb.ca.gov/MyFTBAccess/",
"steps": [
"Watch the mail at the address FTB has on file for the MyFTB PIN letter (5 to 10 business days).",
"Activate before {{expires_on}}: the PIN expires 21 days after registration.",
"Run the command below yourself, with the PIN from the letter."
],
"command": "ftb activate business --pin <PIN from the letter>"
}
}
}
31 changes: 31 additions & 0 deletions apps/logicsrc-web/src/app/openerrand/data.ts
Original file line number Diff line number Diff line change
@@ -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."]
];
Loading
Loading