@frontera-sdk/cli 1.53.5 → 1.53.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/package.json +4 -4
  2. package/src/api/apps-api.ts +2 -2
  3. package/src/api/blueprint-authoring-api.ts +19 -19
  4. package/src/api/governed-action-api.ts +78 -3
  5. package/src/api/platform-api.ts +20 -0
  6. package/src/blueprint/dataset-revision.ts +66 -0
  7. package/src/blueprint/diff.ts +134 -6
  8. package/src/blueprint/publish-plan.ts +253 -0
  9. package/src/blueprint/render.ts +19 -0
  10. package/src/commands/action/deploy.ts +143 -25
  11. package/src/commands/action/index-commands.ts +3 -3
  12. package/src/commands/action/list.ts +3 -3
  13. package/src/commands/action/prepare.ts +5 -3
  14. package/src/commands/action/retire.ts +78 -26
  15. package/src/commands/action/review.ts +86 -25
  16. package/src/commands/app/checks.ts +1 -1
  17. package/src/commands/app/deploy.ts +9 -9
  18. package/src/commands/app/deployments.ts +1 -1
  19. package/src/commands/app/dev.ts +79 -14
  20. package/src/commands/app/promote.ts +1 -1
  21. package/src/commands/app/pull.ts +4 -2
  22. package/src/commands/app/rollback.ts +1 -1
  23. package/src/commands/app/save.ts +3 -7
  24. package/src/commands/app/shared.ts +59 -2
  25. package/src/commands/app/unpin.ts +1 -1
  26. package/src/commands/app/versions.ts +1 -1
  27. package/src/commands/blueprint/authoring.ts +110 -24
  28. package/src/commands/blueprint/bind.ts +10 -46
  29. package/src/commands/blueprint/declarative.ts +124 -30
  30. package/src/commands/blueprint/editable.ts +3 -5
  31. package/src/commands/knowledge/index-commands.ts +15 -2
  32. package/src/commands/local/check.ts +21 -13
  33. package/src/commands/local/data.ts +1 -1
  34. package/src/commands/local/dev.ts +1 -21
  35. package/src/commands/local/index-commands.ts +1 -1
  36. package/src/commands/local/snapshot.ts +2 -2
  37. package/src/commands/local/steps.ts +11 -7
  38. package/src/commands/local/sync-request.ts +45 -8
  39. package/src/commands/local/sync.ts +23 -4
  40. package/src/commands/mcp-server/index-commands.ts +99 -11
  41. package/src/commands/registry.ts +1 -1
  42. package/src/commands/types.ts +2 -0
  43. package/src/device-login.ts +19 -1
  44. package/src/exit.ts +5 -0
  45. package/src/flag-help.ts +2 -1
  46. package/src/forge/permission-rules.ts +2 -0
  47. package/src/local/local-actions.ts +6 -5
  48. package/src/local/local-app-runtime.ts +6 -4
  49. package/src/local/local-blueprint-schemas.ts +2 -2
  50. package/src/local/local-build-inputs.ts +2 -1
  51. package/src/local/local-db-snapshot.ts +62 -46
  52. package/src/local/local-dev-references.ts +0 -23
  53. package/src/local/local-docker.ts +84 -10
  54. package/src/local/local-mcp-servers.ts +79 -14
  55. package/src/local/local-person-steps.ts +121 -19
  56. package/src/local/local-project.ts +5 -1
  57. package/src/local/local-supervisor.ts +41 -3
  58. package/src/local/local-sync-link.ts +76 -0
  59. package/src/local/local-sync-precheck.ts +1 -1
  60. package/src/local/local-sync-push.ts +8 -2
  61. package/src/local/local-sync-remote.ts +18 -0
  62. package/src/main.ts +7 -1
  63. package/src/mcp/mcp-apps.ts +96 -20
  64. package/src/mcp/tools/agent-tools.ts +29 -7
  65. package/src/mcp/tools/app-tools.ts +3 -3
  66. package/src/mcp/tools/project-tools.ts +2 -1
  67. package/src/project.ts +47 -11
  68. package/src/remote-guard.ts +4 -1
  69. package/src/templates/next-app-files.ts +4 -1
  70. package/src/templates/next-skills.ts +9 -5
  71. package/src/vendor/kit-assets.json +8 -8
  72. package/src/vendor/sdk-sources.json +17 -17
@@ -1,5 +1,5 @@
1
1
  {
2
- "kitVersion": "1.5.10",
2
+ "kitVersion": "1.5.12",
3
3
  "cli": {
4
4
  "minimum": "1.45.0",
5
5
  "maximumExclusive": "2.0.0"
@@ -12,7 +12,7 @@
12
12
  "authoring-frontera-agents/agents/openai.yaml": "# Codex presentation metadata. Host-specific by design: SKILL.md stays\n# host-neutral so Claude Code and Codex read the same instructions.\ninterface:\n display_name: Authoring Frontera Agents\n short_description: Compose an agent from models, instructions and reusable resources.\n default_prompt: Give this agent what it needs to answer from our policy documents.\npolicy:\n allow_implicit_invocation: true\n",
13
13
  "authoring-frontera-agents/SKILL.md": "---\nname: authoring-frontera-agents\ndescription: \"Use when creating or changing a Frontera Agent — a conversational or autonomous capability composed from models, instructions, Skills, Plugins, Knowledge, Packs and Blueprint grants. Triggers on outcome language too: \\\"make the support agent able to look up orders\\\", \\\"let it answer from our policies\\\". Covers reading the composition, staging a change onto the draft, seeing the diff against live, and stopping short of publication.\"\n---\n\n# Authoring Frontera Agents\n\nA Frontera Agent is a **conversational or autonomous capability** composed from\na model, its own instructions, and reusable resources — Skills, Knowledge,\nPlugins, Packs and Blueprint grants, each a different kind of thing. For which\nresource carries what, and why installing a Plugin is not the same as granting a\ncapability, read `understanding-frontera`, reference `agents.md`.\n\nAn agent is addressed by id or slug and edited as a document. Nothing is\nimplicit, so nothing goes stale.\n\nLoad `using-frontera` first.\n\n```bash\nfrontera auth current --json\nfrontera help --json\n```\n\n## Read the composition\n\n```bash\nfrontera agent list\nfrontera agent get <agent> # models, prompts, skills, plugins, knowledge\nfrontera agent get <agent> --json > agent.json\n```\n\n## The loop: get, edit, apply, diff\n\n```bash\nfrontera agent get <agent> --json > agent.json\n# edit agent.json\nfrontera agent apply <agent> -f agent.json # stages onto the DRAFT\nfrontera agent diff <agent> # what differs from live\n```\n\n`apply` stages; it does not publish. That separation is the whole safety model —\nyou can iterate on a draft as long as you like and the live agent is untouched.\n\n```bash\nfrontera agent discard <agent> # throw the draft away, live is untouched\nfrontera agent versions <agent> # what has been published before\n```\n\nUse `--expect-revision <hash>` on `apply` when you read the document earlier in\nthe session: it refuses the write if the agent moved, and exits 3 instead of\nsilently overwriting someone.\n\n## The pieces an agent references\n\nEach is its own resource with its own lifecycle — the agent document only names\nthem:\n\n```bash\nfrontera skill list # workspace skills an agent loads at runtime\nfrontera plugin list # integrations and MCP servers connected here\nfrontera knowledge list\nfrontera pack list # reusable skill bundles\n```\n\nKnowledge has one ordering trap worth knowing:\n\n```bash\nfrontera knowledge create <name> --description \"<what is in it>\"\nfrontera knowledge upload <name> ./corpus # a directory is walked\nfrontera knowledge attach <name> <agent> # nothing can read it until this\nfrontera knowledge sources <name> # status per file, minutes later\n```\n\n- **Upload queues ingestion; it does not finish it.** A file comes back\n `processing` with 0 chunks and turns `ready` minutes later. Retrieval tested\n before then returns nothing, which is not a failed upload.\n- **A partial batch still exits 0.** Read `failed` in the payload rather than\n trusting the exit code. Exit is non-zero only when nothing at all landed.\n Re-running is safe — upload is additive.\n- **Attachment is the only route in.** A base nobody is attached to is unreachable.\n\n## Completion evidence\n\n1. `frontera agent diff <agent>` shows exactly the sections you meant to change,\n and no others;\n2. any referenced skill, plugin, knowledge base or pack actually exists — check\n with its `list` command, do not assume a name resolves; and\n3. you reported the diff and said the change is **staged, not live**.\n\n**Do not publish.** `frontera agent publish <agent>` makes it the live version\nfor every conversation using that agent — see `publishing-frontera`.\n\n## Recovery\n\n| Symptom | Move |\n|---|---|\n| exit 3 on `apply` | `frontera agent get` again, reapply your edit, re-apply |\n| exit 2 naming an unknown skill or plugin | list it — the name did not resolve |\n| the draft is wrong and you want out | `frontera agent discard <agent>` |\n| exit 4 | `frontera auth verify` — the key may have been revoked mid-task |\n",
14
14
  "authoring-frontera-apps/agents/openai.yaml": "# Codex presentation metadata. Host-specific by design: SKILL.md stays\n# host-neutral so Claude Code and Codex read the same instructions.\ninterface:\n display_name: Building Frontera Apps\n short_description: Build the operational interface a recurring job needs.\n default_prompt: Operations need a board for today's delayed shipments — build it.\npolicy:\n allow_implicit_invocation: true\n",
15
- "authoring-frontera-apps/SKILL.md": "---\nname: authoring-frontera-apps\ndescription: \"Use when building or changing a Frontera App — an operational interface and code project built on Frontera, reading governed data through the Blueprint SDK and served on the Applications surface. Triggers on outcome language too: \\\"operations need a shipment board\\\", \\\"a page where account managers update status\\\". Covers scaffolding, reading the granted model first, verifying against real data, and deploying a preview without promoting it.\"\n---\n\n# Authoring Frontera Apps\n\nA Frontera App is an **operational interface** — a code project built on\nFrontera that reads governed data through the Blueprint SDK and is served on the\nApplications surface. It is not a Plugin, which connects Frontera to a system\nsomebody else runs. The scaffold is Next.js today and a legacy Vite variant\nexists, but the framework is an authoring detail, not the definition. For the\nconcepts — what a Blueprint grant means for what an App can query — read\n`understanding-frontera`, reference `apps.md`.\n\nLoad `using-frontera` first. Confirm the profile before anything that writes.\n\n```bash\nfrontera auth current --json\nfrontera help --json\n```\n\n## Know the data before designing against it\n\n```bash\nfrontera blueprint list\nfrontera blueprint get <apiName>\n```\n\nWhat these return is the slice this workspace is **granted** — which is exactly\nwhat the App can query at runtime. Anything absent here will be absent for the\nApp too, so design from this output rather than from what the customer says they\nhave.\n\n## The loop\n\n```bash\nfrontera app init <name> # scaffold — installs from public npm only\nbun install\nfrontera app add <component> # registry source into the project\nbun run typecheck\nbun run build # the deploy gate — it must succeed\nfrontera app deploy --preview\n```\n\n`frontera app init` in an existing project adopts it rather than scaffolding a\nsecond one. `frontera app pull <app>` hydrates a working tree from a published\nversion or your saved draft; it refuses to clobber a dirty tree, and inside a\nsandbox there is usually no git to recover from — deal with the files it names.\n\n## Verify against real data, not a screenshot of a refusal\n\n```bash\nfrontera app dev\n```\n\nThis runs the App locally with short-lived authenticated Blueprint access. In a\nlegacy Vite App, opening `/` directly renders \"This app runs inside Frontera\"\ninstead of mounting — the handshake refuses an unknown parent, which is correct\nbehaviour, not a broken build. Open **`/dev-host.html`** instead: it frames the\nApp and plays the host side of the bridge, so the App mounts and reads real data.\nThat is the URL to screenshot or click through.\n\n## The generated SDK tree\n\n`src/frontera/` in a legacy scaffolded project is the SDK, copied in as source so\nthe project installs anywhere. Read it; never edit it, never rewrite an\n`@frontera-sdk/…` import to a relative path, never add `@frontera-sdk/*` to\n`package.json`. `frontera app sdk <action>` refreshes it. A Next App installs the\npublished packages instead and has no such tree.\n\nThe project's own `.agents/skills/` carry the SDK detail — data hooks, tables,\nActions, testing. Read those for anything about writing App code; this skill is\nabout the lifecycle around it.\n\n## Completion evidence\n\nAn App change is done when:\n\n1. `bun run typecheck` and `bun run build` both pass — the Vite build does not\n type-check, so the first is not implied by the second;\n2. the change was seen working against real data through `frontera app dev`;\n3. `frontera app deploy --preview` published an immutable preview deployment; and\n4. you reported the version and told the person what promoting it would do.\n\n**Do not promote.** `frontera app deploy` makes a preview and never changes what\nusers see; `--prod` goes live. Pass `--preview` anyway: a run with a key and no\nterminal must say which it wants. Promotion is a live transition; see\n`publishing-frontera`.\n\n## Recovery\n\n| Symptom | Move |\n|---|---|\n| exit 2, \"not in a Frontera app directory\" | `cd` into the project, or pass `--dir` |\n| build fails after `app add` | read the component source it copied in; it is yours now |\n| deploy rejected | run `bun run build` locally — deploy builds the same output |\n| exit 4 | `frontera auth verify` — the key may have been revoked mid-task |\n",
15
+ "authoring-frontera-apps/SKILL.md": "---\nname: authoring-frontera-apps\ndescription: \"Use when building or changing a Frontera App — an operational interface and code project built on Frontera, reading governed data through the Blueprint SDK and served on the Applications surface. Triggers on outcome language too: \\\"operations need a shipment board\\\", \\\"a page where account managers update status\\\". Covers scaffolding, reading the granted model first, verifying against real data, and deploying a preview without promoting it.\"\n---\n\n# Authoring Frontera Apps\n\nA Frontera App is an **operational interface** — a code project built on\nFrontera that reads governed data through the Blueprint SDK and is served on the\nApplications surface. It is not a Plugin, which connects Frontera to a system\nsomebody else runs. The scaffold is Next.js today and a legacy Vite variant\nexists, but the framework is an authoring detail, not the definition. For the\nconcepts — what a Blueprint grant means for what an App can query — read\n`understanding-frontera`, reference `apps.md`.\n\nLoad `using-frontera` first. Confirm the profile before anything that writes.\n\n```bash\nfrontera auth current --json\nfrontera help --json\n```\n\n## Know the data before designing against it\n\n```bash\nfrontera blueprint list\nfrontera blueprint get <apiName>\n```\n\nWhat these return is the slice this workspace is **granted** — which is exactly\nwhat the App can query at runtime. Anything absent here will be absent for the\nApp too, so design from this output rather than from what the customer says they\nhave.\n\n## The loop\n\n```bash\nfrontera app init <name> # scaffold — installs from public npm only\nbun install\nfrontera app add <component> # registry source into the project\nbun run typecheck\nbun run build # the deploy gate — it must succeed\nfrontera app deploy --preview\n```\n\n`frontera app init` in an existing project adopts it rather than scaffolding a\nsecond one. `frontera app pull <app>` hydrates a working tree from a published\nversion or your saved draft; it refuses to clobber a dirty tree, and inside a\nsandbox there is usually no git to recover from — deal with the files it names.\n\n## Verify against real data, not a screenshot of a refusal\n\n```bash\nfrontera app dev --detach\n```\n\nThis runs the App locally with short-lived authenticated Blueprint access, in\nits own session: it returns once the App answers and prints the URL, the log\nand the stop command, and the server keeps running after your shell ends (a\nbackground command is killed at its time limit). Run it again to get the same\nserver back; `frontera app dev --stop` stops it. Plain `frontera app dev` runs\nin the foreground, for a person's terminal.\n\nIn a legacy Vite App, opening `/` directly renders \"This app runs inside\nFrontera\" instead of mounting — the handshake refuses an unknown parent, which\nis correct behaviour, not a broken build. Open **`/dev-host.html`** instead: it frames the\nApp and plays the host side of the bridge, so the App mounts and reads real data.\nThat is the URL to screenshot or click through.\n\n## The generated SDK tree\n\n`src/frontera/` in a legacy scaffolded project is the SDK, copied in as source so\nthe project installs anywhere. Read it; never edit it, never rewrite an\n`@frontera-sdk/…` import to a relative path, never add `@frontera-sdk/*` to\n`package.json`. `frontera app sdk <action>` refreshes it. A Next App installs the\npublished packages instead and has no such tree.\n\nThe project's own `.agents/skills/` carry the SDK detail — data hooks, tables,\nActions, testing. Read those for anything about writing App code; this skill is\nabout the lifecycle around it.\n\n## Completion evidence\n\nAn App change is done when:\n\n1. `bun run typecheck` and `bun run build` both pass — the Vite build does not\n type-check, so the first is not implied by the second;\n2. the change was seen working against real data through `frontera app dev`;\n3. `frontera app deploy --preview` published an immutable preview deployment; and\n4. you reported the version and told the person what promoting it would do.\n\n**Do not promote.** `frontera app deploy` makes a preview and never changes what\nusers see; `--prod` goes live. Pass `--preview` anyway: a run with a key and no\nterminal must say which it wants. Promotion is a live transition; see\n`publishing-frontera`.\n\n## Recovery\n\n| Symptom | Move |\n|---|---|\n| exit 2, \"not in a Frontera app directory\" | `cd` into the project, or pass `--dir` |\n| build fails after `app add` | read the component source it copied in; it is yours now |\n| deploy rejected | run `bun run build` locally — deploy builds the same output |\n| exit 4 | `frontera auth verify` — the key may have been revoked mid-task |\n",
16
16
  "authoring-frontera-automations/agents/openai.yaml": "# Codex presentation metadata. Host-specific by design: SKILL.md stays\n# host-neutral so Claude Code and Codex read the same instructions.\ninterface:\n display_name: Writing Frontera Automations\n short_description: Express a repeatable business workflow as a code workflow function.\n default_prompt: Implement our nightly reconciliation as a reusable workflow.\npolicy:\n allow_implicit_invocation: true\n",
17
17
  "authoring-frontera-automations/SKILL.md": "---\nname: authoring-frontera-automations\ndescription: \"Use when creating or changing a Frontera Automation — a reusable code workflow function that performs a business workflow on the platform, with typed inputs, declared grants, durable steps and outputs. Triggers on outcome language too: \\\"a nightly reconciliation\\\", \\\"implement the approval workflow as reusable code\\\". Covers scaffolding, serving a working file to dev runs, deploying without promoting, and the kill switch.\"\n---\n\n# Authoring Frontera Automations\n\nAn Automation is a reusable **code workflow function** that performs a business\nworkflow on Frontera — TypeScript deployed to the platform, with typed inputs,\ndeclared grants, durable steps and a return value. The schedule is how it is\ninvoked, never what it is. It has no credential of its own: it reaches\ncapabilities through named grants, and reaches knowledge only through an agent\nthat has the base attached. For the boundary against Agents and against governed\nActions, read `understanding-frontera`, reference `automations-and-actions.md`.\n\nLoad `using-frontera` first.\n\n```bash\nfrontera auth current --json\nfrontera help --json\n```\n\n## Start from something that runs\n\n```bash\nfrontera function init <name>\nfrontera function list\nfrontera function pull <slug> # hydrate an editable project from a deployed version\n```\n\n## Iterate without deploying\n\n```bash\nfrontera function dev ./src/index.ts # serve this file to dev runs\nfrontera function run <slug> --dev # run what dev is serving\nfrontera function runs <slug> # recent runs, and what each returned\n```\n\n`dev` serves your working copy — no deploy, no version, nothing published. This\nis where iteration belongs; every deploy is an immutable version.\n\n## Deploy a preview\n\n```bash\nfrontera function deploy ./src/index.ts --no-promote\nfrontera function versions <slug> # * marks the live one\n```\n\n`frontera function deploy` **promotes live by default**, which is why the\npreview path always passes `--no-promote`.\n\n## Inputs\n\nDeclare a manifest `inputs` schema — `{ name: { type: 'string'|'number'|'boolean'|'object'|'array', required?, default?, description?, enum? } }`, `required` and `default` mutually exclusive — and `ctx.input` is the validated, defaulted, frozen value the run started with (`{}` if `inputs` is absent; a run WITH input against a version with no `inputs` is refused). Cron triggers can only rely on defaults — a required field with none refuses at `deploy` (\"cron has nobody to ask\"). Run one with `frontera function run <slug> --input '{\"k\":1}'` or `--input @file.json`; Console Run-now renders a form from the schema. Dev runs (`--dev`) pass input straight through with only a 64KB size check — no schema check — because the local file, not the last deploy, is authoritative there. Never put a secret in an input: it is stored and shown in the trace by design; use a `secret:` grant instead.\n\n## Secrets and grants\n\n```bash\nfrontera secret set <NAME> --from -\n```\n\nThe automation then **names** the secret — `auth: { secret: 'NAME' }` plus a\n`secret:NAME` grant — and its value never enters the automation's process. An\ninline secret value is refused, and `.env` is never packaged; that is not\noverridable.\n\nA Plugin capability is reached the same way — a `plugin:<install>:<capability>`\ngrant and `ctx.plugin('<install>').call('<capability>', input)`. One grant per\ncapability; the install name is the one `frontera plugin list` prints.\nCapabilities that require approval cannot be granted to an automation (nobody to\nask): `deploy` refuses them. Prefer `ctx.plugin` over `ctx.http` for anything\nan installed Plugin already covers — the credential stays in the platform and\nthe call is governed by the install's policy.\n\n## The kill switch\n\n```bash\nfrontera function disable <slug> # stops scheduled execution immediately\nfrontera function enable <slug> # resumes it\n```\n\nIf a person reports an Automation misbehaving in production, `disable` first and\ndiagnose second. It is reversible; a bad scheduled run may not be.\n\n## Completion evidence\n\n1. a dev run through `frontera function run <slug> --dev` did what was asked,\n and you read `frontera function runs <slug>` to confirm what it returned —\n not just that it exited;\n2. `frontera function deploy … --no-promote` produced a version; and\n3. you reported the version and what promoting it would change.\n\n**Do not promote.** See `publishing-frontera`.\n",
18
18
  "authoring-frontera-blueprint/agents/openai.yaml": "# Codex presentation metadata. Host-specific by design: SKILL.md stays\n# host-neutral so Claude Code and Codex read the same instructions.\ninterface:\n display_name: Changing Frontera Blueprint\n short_description: Change the organization's shared, governed model — safely.\n default_prompt: Make every team calculate active customer the same way.\npolicy:\n allow_implicit_invocation: true\n",
@@ -32,7 +32,7 @@
32
32
  "delivering-frontera-solutions/assets/pending-in-workspace.md": "# Pending in the workspace: <solution name>\n\n> Steps only a person can take, in one list. Keep it current; don't restate it\n> in messages. A console path appears here only when the documentation names\n> it; otherwise give the command or write \"not in the docs, ask <owner>\".\n\n| # | Step | Who | Where | Done when | State |\n|---|---|---|---|---|---|\n| 1 | Apply change request \"<title>\" | <workspace owner> | the change request's page | its steps all read applied | <waiting / done> |\n| 2 | Review and enable new Actions: <names> | <a Blueprint Steward who did not deploy them> | <documented control or command> | each Action reads active | |\n| 3 | Grant <capability> to <role> | <Steward> | <…> | a person in the role can run it | |\n| 4 | Grant sensitive read on <type> to <workspace or Agent> | <Steward> | <…> | a read returns the values, not empty | |\n| 5 | Create public website key for <Agent> with <origins> | <workspace owner> | the Agent's API keys | an allowed origin mints, another is refused | |\n| 6 | Set secret <NAME> | <admin> | `frontera secret set <NAME> --from -` with the workspace key | the Function's call is accepted | |\n| 7 | Promote App <name> | <owner> | <…> | the App opens for its users | |\n| 8 | Bind Function <name> to App <name> | <owner> | <…> | the App's button runs it | |\n\n## Done\n\n| Step | Who did it | When |\n|---|---|---|\n| <…> | <…> | <YYYY-MM-DD> |\n",
33
33
  "delivering-frontera-solutions/assets/solution-brief.md": "# Solution brief: <solution name>\n\n> One page, written with the person who owns the outcome. Every later\n> decision should trace back to a line here: what to build, what an Agent\n> says, what a screen shows, what needs approval. Replace every `<…>`. Write\n> \"unknown: default is …\" rather than inventing a fact.\n\n## The outcome\n\n- **Owner:** <name, role, team at acme>\n- **The job, in one sentence:** <who> needs to <do what> so that <result>.\n- **Today, without it:** <how the job is done now, and what it costs>\n- **Success looks like:** <a number or an observable behaviour, with a date>\n- **Out of scope for this release:** <what was asked for and deferred>\n\n## The people\n\n| Who | Where they meet it | What they need from it | What they must never see |\n|---|---|---|---|\n| <website visitor> | <embed on acme.example> | <answers, a way to reach sales> | <internal notes, other visitors' data> |\n| <sales team> | <Frontera App, team chat> | <new leads with context, fast> | <n/a> |\n\n## How it should sound and behave\n\n- **Voice:** <three adjectives, and one sentence it would say>\n- **Speaks as:** <\"we\" for the organization, or a named assistant speaking for itself>\n- **Languages:** <which, and how it chooses: the visitor's language, the site's language>\n- **Never:** <claims it must not make, topics it declines, data it must not repeat>\n- **Hands to a person when:** <the triggers, to whom, and through which channel>\n- **The surface already shows:** <a greeting, a name, quick actions, so the Agent does not repeat them>\n\n## Data and systems\n\n| Data or system | Owner | Read or write | Confidential fields | How Frontera reaches it |\n|---|---|---|---|---|\n| <Leads> | <sales ops> | <write> | <name, email, phone> | <Blueprint object type> |\n| <CRM, team chat, calendar> | <…> | <…> | <…> | <Plugin, Function, webhook> |\n\n## Governance\n\n- **Changes that need a person's approval:** <writes to systems of record, money, anything irreversible>\n- **Who applies change requests in the workspace:** <name>\n- **Who reads confidential fields:** <roles that need sensitive access>\n- **Keys and secrets needed, and who creates them:** <public website key and its origins, plugin credentials, webhook secrets>\n- **Who hosts and publishes outside Frontera:** <CDN, website builder, DNS>\n\n## Acceptance\n\n- [ ] <a real scenario the owner will try, in their words>\n- [ ] <one where it should decline or hand over>\n- [ ] <the same scenario in each supported language>\n- [ ] <the measurable check from \"success looks like\">\n",
34
34
  "delivering-frontera-solutions/assets/solution-log.md": "# Solution log: <solution name>\n\n> The project's running memory, kept in the repository so every FDE and every\n> coding host reads the same facts. Update it at the end of each working\n> session. Newest entry first. Ids and states, never keys or customer records.\n\n## Now\n\n- **Live in the workspace:** <items and versions>\n- **Waiting on someone:** <change request \"<title>\": the owner applies it> · <public key: the owner creates it with origins …>\n- **Next:** <the next outcome to build>\n\n## Customer rules\n\n> Decisions this customer made, usually as corrections. Every session reads\n> them first and applies them without being asked. Agent rules also live in\n> the Agent's instructions, each with a test case.\n\n| Rule | Applies to | Came from | Date |\n|---|---|---|---|\n| <the assistant speaks as \"we\" for acme> | <Agent voice> | <owner, review of the first replies> | <YYYY-MM-DD> |\n| <greys for secondary controls; brand colour only for the primary action> | <App, embed> | <owner correction> | <YYYY-MM-DD> |\n\n## Kit candidates\n\n> Corrections that would hold for any customer. Share them with whoever\n> maintains the Frontera kit, so every FDE gets them.\n\n- <an example sentence in instructions gets copied word for word>\n\n## Reference\n\n| What | Id or value | Note |\n|---|---|---|\n| Workspace | <name> | <linked in frontera.yaml> |\n| Agent `<slug>` | <id> | <published version> |\n| Open change request | <id> | <title> |\n| Public website key | <prefix only> | <origins it allows> |\n\n## Entries\n\n### <YYYY-MM-DD>: <one line on what this session did>\n\n- **Changed:** <items, and why>\n- **Pushed:** <request title, the items in it, what was left out and why>\n- **Learned:** <a gotcha, its cause and the workaround, so nobody pays for it twice>\n- **Pending:** <what, whose action>\n",
35
- "delivering-frontera-solutions/references/delivery.md": "# Delivering to the workspace\n\nHow a solution built in a project folder reaches the customer's workspace:\nwhat to settle before the first push, the order to push in, how to run each\npush, and how to handle the steps only a person can take.\n\n## Preflight before the first push\n\nAnswer each of these before anything is sent, and write the answers into\n`assets/change-request-plan.md`:\n\n- **What sync carries in this release, and what it does not.** Editable\n fields, test cases that wait between turns and new Actions' deployment\n decisions may need a step in the workspace instead. Saved object sets\n (`blueprint/object-sets/`) and a Custom App's settings (`apps/<slug>.json`:\n its name, icon and the Functions it may start) go in a change request. A\n dataset goes as its columns, never its rows: a blank `datasets/<apiName>.json`,\n or a file dataset whose file gives every included column its `databaseType`\n and `nullable`. It is listed with the object type that reads it, and Apply\n creates it only when the organization has none of that name. A Source\n dataset is made on the platform.\n- **Which credential each step needs.** Organization-level Blueprint changes\n and grants may need an organization key; workspace items a workspace key;\n some steps need a signed-in person. A step the project's key cannot do\n rolls back the whole request it sits in.\n- **Which steps need a person, or a second person.** Reviewing and enabling\n new Actions, granting sensitive access, promoting an App to production.\n List each in `assets/pending-in-workspace.md` with its owner.\n- **Names that collide.** A workspace sees only its slice of the\n organization's Blueprint. Prefer specific API names (`SalesActivity`, not\n `Activity`) and check them against the whole organization.\n- **References that must resolve on the target**: models, saved sets,\n Functions.\n\n## Push order\n\n1. Datasets and object types; publish the Blueprint.\n2. Editable fields, saved sets, workspace grants.\n3. Agents without Blueprint grants, then their grants.\n4. Functions (promoted), workflows, App bindings.\n5. The App preview, then its promotion.\n6. New Actions: deploy, review, enable, grant capabilities.\n\nKeep each request small and to one kind where possible: one failing step\nrolls back everything in it.\n\n```bash\nfrontera local sync status --fetch\nfrontera local sync push --preview\nfrontera local sync push agent/<slug> evaluation/<name> -t \"<what changes for whom>\"\nfrontera local sync request evidence --evals --renders\n```\n\n- **Push only what was asked, by name.** `push --preview` lists everything\n that differs. Items that differ only by defaults or formatting are not\n changes. Leave them out and say so, rather than sweeping them into a\n request nobody asked for.\n- **One request per outcome**, titled for the person applying it (\"Faster\n first reply for new leads\"), not for the files (\"update agent.json\").\n `assets/change-request-plan.md` lists them in dependency order.\n- **Dependencies land first.** A Function must be promoted before a request\n binds it. A workflow that names a saved object set goes in the same request\n as the set's file, which Apply saves before the workflow; the set's object\n type must be granted to the workspace or added by that request too. A\n static set's records are made once: a new list needs a new set.\n- **Model names differ between runtimes.** When the local model's slug is not\n the workspace's, map it once with `frontera local sync map-model` instead of\n editing files back and forth.\n- **Attach the evidence.** `request evidence --evals --renders` puts test\n results and App screenshots on the request, so the person applying it can\n judge it without re-running anything.\n- **Publishing, promoting and applying are the person's calls.** Prepare\n freely; take a live step only on an explicit request for that step. See\n `publishing-frontera`. Approval for one push does not carry to the next.\n\n## Person-steps\n\nSome steps only a person can take. Make them easy and finite:\n\n- **Prepare each one as a person-step.** In a project made by\n `frontera local`, the project's MCP tool `person_step` (or\n `frontera local steps add \"<command>\" --why … --changes … --credential …\n --delivery \"<request title>\"`) records the command, why it is needed, what\n it changes and the credential it needs. Forge shows the delivery's steps as\n one card, and the person runs each from it in their own terminal. Prepare the\n whole delivery at once, in order; never run a step yourself.\n `frontera local steps` lists what is still pending.\n- **One row each** in `assets/pending-in-workspace.md` too, for steps outside\n the CLI: what, who, where (a console path the docs name, or a CLI command),\n and how to tell it is done. Keep that one list current instead of restating\n it in messages.\n- **Give a console path only when the documentation names it.** When you are\n not sure a button exists, give the command or say the docs do not cover it.\n- **Never ask anyone to paste a key, token or session into chat**, or to copy\n one out of a browser. Secrets are piped\n (`openssl rand -hex 32 | frontera secret set NAME --from -`), and person-only\n steps happen in the product.\n- **Finish what you can end to end.** Run every step the project's\n credentials allow, then hand over only what truly needs the person, in one\n list. Drip-feeding commands back to the person turns them into your\n terminal.\n- **Check for done yourself** where you can (list the Actions, read the\n record) rather than asking whether it happened.\n",
35
+ "delivering-frontera-solutions/references/delivery.md": "# Delivering to the workspace\n\nHow a solution built in a project folder reaches the customer's workspace:\nwhat to settle before the first push, the order to push in, how to run each\npush, and how to handle the steps only a person can take.\n\n## Preflight before the first push\n\nAnswer each of these before anything is sent, and write the answers into\n`assets/change-request-plan.md`:\n\n- **What sync carries in this release, and what it does not.** Editable\n fields, test cases that wait between turns and new Actions' deployment\n decisions may need a step in the workspace instead. Saved object sets\n (`blueprint/object-sets/`) and a Custom App's settings (`apps/<slug>.json`:\n its name, icon and the Functions it may start) go in a change request. A\n dataset goes as its columns, never its rows: a blank `datasets/<apiName>.json`,\n or a file dataset whose file gives every included column its `databaseType`\n and `nullable`. It is listed with the object type that reads it, and Apply\n creates it only when the organization has none of that name. A Source\n dataset is made on the platform. A workspace MCP server\n (`mcp-servers/<slug>.yaml`) is not in a change request: `frontera\n mcp-server save <file>` saves it to the workspace, and in Forge the person\n approves that save.\n- **Which credential each step needs.** Organization-level Blueprint changes\n and grants may need an organization key; workspace items a workspace key;\n some steps need a signed-in person. A step the project's key cannot do\n rolls back the whole request it sits in.\n- **Which steps need a person, or a second person.** Turning on new\n Actions: the person may review their own Action when it only edits\n Blueprint records; an Action that calls another system needs a second\n person, and so does the write path that follows one. Granting sensitive\n access, promoting an App to production. List each in\n `assets/pending-in-workspace.md` with its owner.\n- **`frontera action deploy` and `frontera action retire` are person-steps.**\n They turn an Action's write path on or switch it off, as the person. Record\n each as a person-step and never run them with `--yes` yourself, even when a\n command you ran prints that line as its next step.\n- **Names that collide.** A workspace sees only its slice of the\n organization's Blueprint. Prefer specific API names (`SalesActivity`, not\n `Activity`) and check them against the whole organization.\n- **References that must resolve on the target**: models, saved sets,\n Functions.\n\n## Push order\n\n1. Datasets and object types; publish the Blueprint.\n2. Editable fields, saved sets, workspace grants.\n3. Agents without Blueprint grants, then their grants.\n4. Functions (promoted), workflows, App bindings.\n5. The App preview, then its promotion.\n6. New Actions: deploy, review, enable, grant capabilities.\n\nKeep each request small and to one kind where possible: one failing step\nrolls back everything in it.\n\n```bash\nfrontera local sync status --fetch\nfrontera local sync push --preview\nfrontera local sync push agent/<slug> evaluation/<name> -t \"<what changes for whom>\"\nfrontera local sync request evidence --evals --renders\n```\n\n- **Push only what was asked, by name.** `push --preview` lists everything\n that differs. Items that differ only by defaults or formatting are not\n changes. Leave them out and say so, rather than sweeping them into a\n request nobody asked for.\n- **One request per outcome**, titled for the person applying it (\"Faster\n first reply for new leads\"), not for the files (\"update agent.json\").\n `assets/change-request-plan.md` lists them in dependency order.\n- **Dependencies land first.** A Function must be promoted before a request\n binds it. A workflow that names a saved object set goes in the same request\n as the set's file, which Apply saves before the workflow; the set's object\n type must be granted to the workspace or added by that request too. A\n static set's records are made once: a new list needs a new set.\n- **Model names differ between runtimes.** When the local model's slug is not\n the workspace's, map it once with `frontera local sync map-model` instead of\n editing files back and forth.\n- **Attach the evidence.** `request evidence --evals --renders` puts test\n results and App screenshots on the request, so the person applying it can\n judge it without re-running anything.\n- **Publishing, promoting and applying are the person's calls.** Prepare\n freely; take a live step only on an explicit request for that step. See\n `publishing-frontera`. Approval for one push does not carry to the next.\n\n## Person-steps\n\nSome steps only a person can take. Make them easy and finite:\n\n- **Prepare each one as a person-step.** In a project made by\n `frontera local`, the project's MCP tool `person_step` (or\n `frontera local steps add \"<command>\" --why … --changes … --credential …\n --delivery \"<request title>\"`) records the command, why it is needed, what\n it changes and the credential it needs. Forge shows the delivery's steps as\n one card, and the person runs each from it in their own terminal. Prepare the\n whole delivery at once, in order; never run a step yourself.\n `frontera local steps` lists what is still pending.\n- **One row each** in `assets/pending-in-workspace.md` too, for steps outside\n the CLI: what, who, where (a console path the docs name, or a CLI command),\n and how to tell it is done. Keep that one list current instead of restating\n it in messages.\n- **Give a console path only when the documentation names it.** When you are\n not sure a button exists, give the command or say the docs do not cover it.\n- **Never ask anyone to paste a key, token or session into chat**, or to copy\n one out of a browser. Secrets are piped\n (`openssl rand -hex 32 | frontera secret set NAME --from -`), and person-only\n steps happen in the product.\n- **Finish what you can end to end.** Run every step the project's\n credentials allow, then hand over only what truly needs the person, in one\n list. Drip-feeding commands back to the person turns them into your\n terminal.\n- **Check for done yourself** where you can (list the Actions, read the\n record) rather than asking whether it happened.\n",
36
36
  "delivering-frontera-solutions/SKILL.md": "---\nname: delivering-frontera-solutions\ndescription: Use when building a whole customer solution in a Frontera project folder, from brief to live workspace: build order, quality gates, delivery preflight, change requests and handover.\n---\n\n# Delivering Frontera solutions\n\nA solution is several artifacts working as one: Blueprint records, Functions\nand workflows that change them, Agents that talk and decide, knowledge they\nanswer from, and the Apps or embeds people actually touch. This skill is the\norder to build them in, the gate each one passes, and how the result reaches\nthe customer's workspace without surprises.\n\nLoad `using-frontera` first. Before anything that writes:\n\n```bash\nfrontera auth current --json\nfrontera local status --json\n```\n\nThe first says which customer's credential this folder selects. The second\nsays whether the local runtime is up and which personas, models and items it\nholds. When the project's `frontera` MCP server is offered, use its tools\n(`status`, `apply`, `test_run`, `logs`, `read_file_schema`) instead of the\nshell.\n\n## The shape of the work\n\n1. **Brief before files.** Fill `assets/solution-brief.md` with the person\n who owns the outcome: who uses it, the one job it does, what \"good\" sounds\n like, what it must never do, which systems it touches and what data is\n confidential. Every later decision traces back to a line in it. If a line\n is unknown, say so and pick a stated default; do not invent the customer's\n facts.\n2. **Choose the artifacts.** Map each job to one artifact with\n `understanding-frontera` (reference `choosing-what-to-build.md`). Write the\n map into `assets/build-plan.md`: one primary artifact per outcome, the rest\n as dependencies.\n3. **Build in dependency order**, each step verified before the next:\n\n | Order | Artifact | Skill | Gate before moving on |\n |---|---|---|---|\n | 1 | Blueprint: object types, links, Actions, sets, grants | `designing-frontera-blueprint`, `authoring-frontera-blueprint` | applies cleanly; sample data loads with links intact |\n | 2 | Functions | `writing-frontera-functions` | unit tests pass; a dry call returns the shape the caller reads |\n | 3 | Workflows | `designing-frontera-workflows` | each workflow test reaches `succeeded` on sample data |\n | 4 | Knowledge | `designing-frontera-agents` (reference `knowledge.md`) | every document is ready; real questions retrieve the right page |\n | 5 | Agents and their test cases | `designing-frontera-agents` | gated test cases pass several runs in a row |\n | 6 | Frontera Apps | `designing-frontera-app-experiences`, `authoring-frontera-apps` | `bun run check` passes; rendered states reviewed at phone and desktop widths |\n | 7 | External embeds | `building-frontera-embeds` | works from an allowed origin, refuses others, reviewed on a phone |\n\n Records first, because every Function, workflow, Agent and App reads or\n writes them; people-facing surfaces last, because they are only as good as\n what is under them.\n4. **Verify the whole thing locally**, then **deliver it in small, named\n change requests**, then **hand it over.** Each has its own section below.\n\n## The local loop\n\n```bash\nfrontera local dev # applies every saved file and keeps watching\nfrontera local test # Agent test cases and workflow tests on the local runtime\nfrontera local check # the whole gate, one exit code\n```\n\n- **`local dev` is the feedback loop; read what it skips.** A file it does not\n apply is named with where it belongs. A skipped file is never applied, and\n a skipped file you believe is live is the most expensive bug in this loop.\n- **Schemas are served, not guessed.** `frontera local schema <kind>` (MCP\n `read_file_schema`) prints the exact format of every file kind. Read it\n before writing a file kind for the first time in a session.\n- **Sample data is part of the solution.** `frontera local data --synthetic`\n writes linked, consistent rows; adjust `data/generate.ts` until every state\n a person or Agent must handle exists in the data, including the awkward\n ones.\n- **Test writes are real here.** A workflow test that submits an Action\n changes local records. `frontera local check` rolls its own writes back;\n `frontera local snapshot save` / `restore` do it by hand.\n- **One runtime per machine is the expensive part.** Run tests one suite at\n a time and lower `--concurrency` when the model provider refuses requests.\n\n`frontera local check` passing is the evidence that the project works. Report\nwhich steps ran and which were skipped. \"It applied\" is not \"it works\".\n\n## Proving it works\n\nVerification is about what a person sees, not about what a command printed:\n\n- **Agents:** read the actual replies from the test run, not only the pass\n count. A passing regex over a bad reply is a bad test. `test_run` with\n `transcripts`, `conversation_get` and `trace_get` (or `frontera\n conversation get` and `frontera trace get`) give every turn, tool call,\n procedure and reply check; never query the runtime's database, and never\n ask the person to paste a conversation the runtime has.\n- **Workflows and Functions:** open the run's output and the records it wrote.\n The status says it finished, not that it wrote the right thing.\n- **Apps and embeds:** look at the rendered screen in each state (loading,\n empty, error, full, long text) at phone and desktop widths. A screenshot is\n evidence; a passing build is not.\n- **Grants:** read through the same slice the Agent or App will use. A field\n marked confidential reads as null to anyone without sensitive access. That\n is correct behaviour, so plan the grant before showing anyone the screen.\n\n## Delivering to the workspace\n\nThe project links to one workspace. Changes reach it as **change requests**,\nwhich a workspace owner applies. Delivery has cost more than building on\nreal projects. Plan it on the first day, not after the last feature.\n\nRead `references/delivery.md` before the first push. In short:\n\n- **Preflight first**: what sync carries in this release, which credential\n each step needs, which steps need a person or a second person, names that\n collide across the organization, and references that must resolve on the\n target. Write the answers into `assets/change-request-plan.md`.\n- **Push in dependency order**, in small requests of one kind where possible:\n one failing step rolls back everything in the request.\n- **Preview, then push only what was asked, by name**, with test results and\n App screenshots attached as evidence.\n- **Person-steps are prepared, not described.** Record each with the MCP tool\n `person_step` (or `frontera local steps add`) so the person runs it from\n Forge's delivery card; steps outside the CLI go in\n `assets/pending-in-workspace.md`, with an owner, a documented console path\n and a done-check. Finish everything the project's credentials allow before\n handing over the rest.\n- **Never ask anyone to paste a key, token or session into chat.**\n- **Publishing, promoting and applying are the person's calls.** See\n `publishing-frontera`.\n\n```bash\nfrontera local sync push --preview\nfrontera local sync request evidence --evals --renders\n```\n\n## Working rules\n\n- **Change only what was asked.** Propose related improvements separately.\n- **Fix a reproducible bug in local files** and run the tests before and\n after; don't ask three times whether to fix it.\n- **Confirm scope in one line before reversing a working design.**\n- **User-facing quality beats verification convenience.** Build the\n immediate path the person needs, and say what could not be verified\n locally, instead of shipping the version that was easier to test.\n- **Verify a code finding is in the release the workspace runs** before\n stating it as fact. Read platform behaviour from the documentation for\n the installed release, not from other folders on the machine.\n- **Separate what you report:** checks passed, replies read, visually\n verified, pushed, applied, live.\n\n## Keep the project's memory in the project\n\nDecisions, ids, gotchas and \"still to do\" belong in the repository, where the\nnext FDE and every coding host can read them, not in one person's chat\nhistory or machine-local memory. Keep `docs/solution-log.md` from\n`assets/solution-log.md`. Update it at the end of every working session: what\nchanged, what is live, what is pending whose action, and what surprised you.\n\n## Handing over\n\nA solution is delivered when the customer can run it without you. Fill\n`assets/handover-runbook.md`: what is live, who owns each part, how to change\nan Agent's behaviour safely, what to watch, the known limits and how to roll\nback. Walk the owner through one real change using the runbook alone. Run\n`assets/go-live-checklist.md` before the first real user arrives.\n\n## Before you report done\n\nRun the review in `reviewing-frontera-work` after each push and before calling anything live, with its\n`rubric-delivery.md` rubric. When the person corrects you, record the\ncorrection as that skill describes, so it is applied next time\nwithout being asked.\n\n## Red flags\n\n- Building the Agent before the records it reads exist.\n- A test suite that only ever passes, or one rewritten to pass instead of the\n reply being fixed.\n- \"It's live\" without having read it back through the workspace.\n- A change request that carries items nobody asked to change.\n- A secret, key or customer record pasted into a file, a test case or a chat.\n- Ids, decisions and pending steps that live only in a chat transcript.\n\n## Assets and references\n\n| File | Use it for |\n|---|---|\n| `assets/solution-brief.md` | the one-page brief every later decision traces to |\n| `assets/build-plan.md` | artifact map, build order and gates |\n| `assets/change-request-plan.md` | which requests, in what order, with what evidence |\n| `assets/pending-in-workspace.md` | every step only a person can take, with owner and done-check |\n| `assets/go-live-checklist.md` | the checks before the first real user |\n| `assets/handover-runbook.md` | what the customer's owner needs to run it alone |\n| `assets/solution-log.md` | the project's running memory, kept in the repository |\n| `references/delivery.md` | preflight, push order, pushing, person-steps |\n",
37
37
  "designing-frontera-agents/agents/openai.yaml": "# Codex presentation metadata. Host-specific by design: SKILL.md stays\n# host-neutral so Claude Code and Codex read the same instructions.\ninterface:\n display_name: Designing Frontera Agents\n short_description: Persona, instructions, procedures, approvals and test cases that hold up.\n default_prompt: Our website assistant sounds stiff and asks for confirmation twice. Fix it properly.\npolicy:\n allow_implicit_invocation: true\n",
38
38
  "designing-frontera-agents/assets/agent-brief.md": "# Agent brief: <agent name>\n\n> Fill this before writing instructions. Every rule and every test case should\n> trace back to a line here. Write \"unknown: default is …\" rather than\n> inventing a fact.\n\n## The job\n\n- **Kind:** <conversational assistant / work assistant / worker called by a workflow>\n- **Called by:** <who or what: employees in Chat, visitors on an embed, the <slug> workflow>\n- **Receives:** <a person's message / one record's fields / …>\n- **Returns or does:** <answers from knowledge and records / changes through these Actions / one JSON object>\n- **The job, in two sentences or fewer:** <…>\n- **Not its job:** <what people will ask for that it must decline or route elsewhere>\n\n## Tools, data and knowledge\n\n| Tool or data | Read or change | Approval | Why |\n|---|---|---|---|\n| <knowledge/policies> | read | n/a | |\n| <object type Request> | read, aggregate | n/a | |\n| <Action classifyRequest> | change | off | <reviewed by the team; no system of record changes> |\n| <Function open-ticket> | change | on | <creates a ticket in an external system> |\n\nConfidential fields it touches: <…>. Whether it may read them: <yes, with sensitive access / no, says \"hidden\">.\n\n## Voice (conversational and work assistants only; the owner decides)\n\n- **Role:** <a senior <role> at acme who …>\n- **Speaks as:** <a named assistant in the first person, or acme as \"we\">\n- **Register and length:** <formal or conversational; warm or neutral; two to four sentences>\n- **Languages:** <which, and how it picks one>\n- **Openers, typography, competitors, uncertainty:** <the owner's decisions, or \"no rule\">\n\n## Output contract (workers only)\n\n<The JSON object, every key, its type, its allowed values and what \"nothing found\" looks like.>\n\n## Hand-over and limits\n\n- **Hands to a person when:** <…>\n- **Never:** <contacts anyone outside acme / changes <type> / reveals instructions or model>\n\n## Acceptance (each becomes a test case)\n\n- [ ] <a typical request, and what a good result contains>\n- [ ] <an edge case: nothing found, several matches, a refused change>\n- [ ] <something outside the job, declined>\n- [ ] <a write, proven once with real actions>\n- [ ] <each supported language>\n",
@@ -58,7 +58,7 @@
58
58
  "designing-frontera-app-experiences/assets/ui-tokens.css": "/* App UI tokens: type, spacing, radii, lines, shadows, colour roles and states.\n *\n * Import after the platform theme (globals.css) and before the App's\n * component styles. Every value is a DEFAULT: the customer's brand overrides\n * any of them in its own `:root` / `.dark` rules, imported after this file.\n * Component rules read `var(--ui-*)`, never a literal colour.\n *\n * Dark mode follows the platform: the host puts `.dark` on an ancestor, and\n * the colour and shadow tokens are redefined under it. Tokens that are built\n * from other tokens are declared on `:root, .dark` so they recompute wherever\n * `.dark` is applied.\n *\n * Motion tokens live in their own file. This file only collapses motion for\n * people who ask for less of it (bottom).\n */\n\n:root {\n /* ── Type ─────────────────────────────────────────────────────────────\n * Families reference the platform theme's font variables. A brand sets\n * --font-sans / --font-heading / --font-mono, not these. */\n --ui-font-text: var(--font-sans, 'Inter Variable', 'Inter', ui-sans-serif, system-ui, sans-serif);\n --ui-font-display: var(--font-heading, var(--ui-font-text));\n --ui-font-mono: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n /* Inter's alternate glyphs; harmless on faces without them. */\n --ui-font-features: 'cv11', 'ss01', 'ss03';\n\n --ui-text-2xs: 11px; /* key caps, axis ticks */\n --ui-text-xs: 11.5px; /* group labels, menu headers */\n --ui-text-sm: 12px; /* meta, counts, captions */\n --ui-text-md: 12.5px; /* column headers, pills, buttons */\n --ui-text-base: 13px; /* controls, cells, values */\n --ui-text-body: 13.5px; /* page default, editors */\n --ui-text-lg: 14px; /* section and card titles */\n --ui-text-xl: 15px; /* dialog and empty-state titles */\n --ui-text-title: 16px; /* toolbar title */\n --ui-text-display-sm: 20px; /* record title in a panel */\n --ui-text-display: 24px; /* headline figure */\n --ui-text-display-lg: 26px; /* record title on a page */\n\n --ui-weight-regular: 400;\n --ui-weight-medium: 500;\n --ui-weight-semibold: 600;\n\n --ui-leading-figure: 1.1;\n --ui-leading-tight: 1.2;\n --ui-leading-snug: 1.25; /* two-line cells */\n --ui-leading-tip: 1.35;\n --ui-leading-base: 1.45;\n --ui-leading-reading: 1.55;\n\n /* Titles tighten as they grow. */\n --ui-tracking-lg: -0.008em; /* 14px */\n --ui-tracking-xl: -0.01em; /* 15px */\n --ui-tracking-title: -0.012em; /* 16px */\n --ui-tracking-display-sm: -0.018em; /* 20px */\n --ui-tracking-display: -0.022em; /* 24px */\n --ui-tracking-display-lg: -0.024em; /* 26px and up */\n\n /* ── Spacing ──────────────────────────────────────────────────────── */\n --ui-space-1: 2px;\n --ui-space-2: 4px;\n --ui-space-3: 6px;\n --ui-space-4: 8px;\n --ui-space-5: 10px;\n --ui-space-6: 12px;\n --ui-space-7: 14px;\n --ui-space-8: 16px;\n --ui-space-9: 18px;\n --ui-space-10: 20px;\n --ui-space-11: 24px;\n --ui-space-12: 28px;\n --ui-space-13: 48px;\n\n /* ── Density ──────────────────────────────────────────────────────── */\n --ui-control-2xs: 20px; /* count badges, meta pills */\n --ui-control-xs: 22px; /* pills, tags, inline buttons */\n --ui-control-sm: 24px; /* card menu, choice toggles */\n --ui-control-md: 28px; /* buttons, segmented tabs */\n --ui-control-lg: 30px; /* search, chips, icon buttons */\n --ui-control-xl: 32px; /* nav and menu items, inputs, cell editors */\n --ui-row-header: 34px; /* table header, property rows */\n --ui-row: 44px; /* table rows, filter bar, panel bar */\n --ui-toolbar: 52px;\n --ui-touch-target: 44px; /* minimum hit area on coarse pointers */\n\n /* ── Radii ─────────────────────────────────────────────────────────\n * Nest by subtraction: inner = outer − the padding between them. */\n --ui-radius-2xs: 4px; /* skeleton bars, checkboxes */\n --ui-radius-xs: 5px; /* key caps, small counts */\n --ui-radius-sm: 6px; /* pills, tags, menu items */\n --ui-radius-md: 7px; /* buttons, tabs, cell editors, tooltips */\n --ui-radius-lg: 8px; /* inputs, search, chips, nav items */\n --ui-radius-xl: 10px; /* popovers, cards, text areas */\n --ui-radius-2xl: 12px; /* main surface, columns, chart cards */\n --ui-radius-3xl: 14px; /* side panels, dialogs */\n --ui-radius-full: 999px;\n\n /* ── Lines ─────────────────────────────────────────────────────────── */\n --ui-line-width: 1px;\n --ui-line-emphasis: 1.5px; /* drop target, keyboard-active card */\n --ui-indicator: 2px; /* keyboard-active row bar */\n\n /* ── Colour roles (light) ─────────────────────────────────────────── */\n --ui-canvas: #f6f7f8;\n --ui-paper: #ffffff;\n --ui-fill: #eff1f3;\n\n --ui-ink: #101010;\n --ui-ink-secondary: #2f3337;\n --ui-ink-muted: #6b7177;\n --ui-ink-faint: #70767c; /* still 4.5:1 on paper */\n\n --ui-line: rgba(16, 24, 40, 0.08);\n --ui-line-strong: rgba(16, 24, 40, 0.14);\n\n --ui-primary: #2a2e33;\n --ui-primary-hover: #1d2024;\n --ui-on-primary: #ffffff;\n\n /* Links and focus only. */\n --ui-accent: #006fe6;\n /* Strengths of the accent tints (derived below), so a brand accent carries. */\n --ui-accent-soft-mix: 10%;\n --ui-accent-line-mix: 55%;\n --ui-ring-mix: 18%;\n\n --ui-tip: #101010;\n --ui-on-tip: #ffffff;\n --ui-scrim: rgba(16, 16, 16, 0.12);\n --ui-skeleton: rgba(16, 24, 40, 0.07);\n\n --ui-danger: #b42318;\n --ui-danger-bg: #fef3f2;\n --ui-success: #1d8a4e;\n\n /* Status tones: background, foreground, dot. A pill draws its own dot in\n * currentColor, so only the tones shown as bare dots carry a dot token. */\n --ui-status-attention-bg: #fbf3e4;\n --ui-status-attention-fg: #85570b;\n --ui-status-attention-dot: #e0a23a;\n --ui-status-positive-bg: #e9f5ee;\n --ui-status-positive-fg: #1d6b40;\n --ui-status-positive-dot: #3aa66b;\n --ui-status-progress-bg: #efedfa;\n --ui-status-progress-fg: #4a3fa3;\n --ui-status-progress-dot: #7b6fe0;\n --ui-status-inactive-bg: #f1f2f3;\n --ui-status-inactive-fg: #62686e;\n --ui-status-inactive-dot: #a7adb3;\n --ui-status-neutral-bg: #f1f2f3;\n --ui-status-neutral-fg: #4b5157;\n --ui-status-info-bg: #e8f1fd;\n --ui-status-info-fg: #1d5fb8;\n --ui-status-negative-bg: #fdf0ee;\n --ui-status-negative-fg: #9b3a2a;\n --ui-status-paused-bg: #f4f4f2;\n --ui-status-paused-fg: #6b6a64;\n\n /* Signal levels (three-bar marks) and score meter bands. */\n --ui-level-high: #c4421a;\n --ui-level-mid: #2f3337;\n --ui-level-low: #9aa0a6;\n --ui-meter-track: rgba(16, 24, 40, 0.09);\n --ui-meter-good: #2f9e62;\n --ui-meter-fair: #d39a2c;\n --ui-meter-poor: #c4421a;\n\n /* Avatar tones, picked by a stable hash of the name. */\n --ui-avatar-0-bg: #eef1fb; --ui-avatar-0-fg: #3b4f9c;\n --ui-avatar-1-bg: #eaf5ef; --ui-avatar-1-fg: #26704a;\n --ui-avatar-2-bg: #fbf1e6; --ui-avatar-2-fg: #8a5419;\n --ui-avatar-3-bg: #f6ecf4; --ui-avatar-3-fg: #8a3a77;\n --ui-avatar-4-bg: #e8f4f6; --ui-avatar-4-fg: #22677a;\n --ui-avatar-5-bg: #f2efe9; --ui-avatar-5-fg: #5f5546;\n --ui-avatar-6-bg: #efedfa; --ui-avatar-6-fg: #4a3fa3;\n --ui-avatar-7-bg: #fdeeee; --ui-avatar-7-fg: #9b3434;\n\n /* Charts: one accent, an ordinal ramp (light → strong), a neutral rest. */\n --ui-viz: #1f74e0;\n --ui-viz-1: #88b5ef;\n --ui-viz-2: #3d87e8;\n --ui-viz-3: #0058b8;\n --ui-viz-rest: #d5d9de;\n --ui-viz-track: rgba(16, 24, 40, 0.05);\n --ui-viz-grid: rgba(16, 24, 40, 0.07);\n --ui-on-viz: #ffffff;\n\n /* ── States (light) ───────────────────────────────────────────────── */\n --ui-hover-area: rgba(16, 24, 40, 0.035); /* rows, header cells, chart columns */\n --ui-hover: rgba(16, 24, 40, 0.055); /* controls, highlighted menu item */\n --ui-selected: rgba(16, 24, 40, 0.07); /* the open item in a list */\n --ui-disabled-opacity: 0.4;\n\n /* ── Shadows (light) ──────────────────────────────────────────────── */\n --ui-shadow-control: 0 1px 3px rgba(0, 0, 0, 0.04), inset 0 0 0 1px rgba(0, 0, 0, 0.08);\n --ui-shadow-card: 0 0 0 1px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);\n --ui-shadow-card-hover: 0 0 0 1px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.05), 0 8px 24px rgba(0, 0, 0, 0.06);\n --ui-shadow-surface: 0 0 0 1px rgba(0, 0, 0, 0.06), 0 2px 6px rgba(0, 0, 0, 0.05), 0 4px 42px rgba(0, 0, 0, 0.06);\n --ui-shadow-surface-hover: 0 0 0 1px rgba(0, 0, 0, 0.07), 0 2px 6px rgba(0, 0, 0, 0.06), 0 6px 42px rgba(0, 0, 0, 0.1);\n --ui-shadow-button: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.12);\n --ui-shadow-tip: 0 4px 16px rgba(0, 0, 0, 0.12);\n --ui-shadow-avatar: inset 0 0 0 1px rgba(0, 0, 0, 0.04);\n --ui-shadow-avatar-image: inset 0 0 0 1px rgba(16, 24, 40, 0.1);\n}\n\n.dark {\n /* ── Colour roles (dark) ──────────────────────────────────────────── */\n --ui-canvas: #0f1012;\n --ui-paper: #16181b;\n --ui-fill: #1a1c1f;\n\n --ui-ink: #f2f3f4;\n --ui-ink-secondary: #d6d9dc;\n --ui-ink-muted: #9aa0a6;\n --ui-ink-faint: #868c92;\n\n --ui-line: rgba(255, 255, 255, 0.07);\n --ui-line-strong: rgba(255, 255, 255, 0.12);\n\n --ui-primary: #e8eaec;\n --ui-primary-hover: #ffffff;\n --ui-on-primary: #101010;\n\n --ui-accent: #4c9dff;\n --ui-accent-soft-mix: 14%;\n --ui-accent-line-mix: 60%;\n --ui-ring-mix: 25%;\n\n --ui-tip: #f2f3f4;\n --ui-on-tip: #101010;\n --ui-scrim: rgba(0, 0, 0, 0.35);\n --ui-skeleton: rgba(255, 255, 255, 0.07);\n\n --ui-danger: #f97066;\n --ui-danger-bg: rgba(249, 112, 102, 0.1);\n --ui-success: #4cc283;\n\n /* Tints become the hue at 13–16% over dark paper, with a lighter foreground.\n * Dots keep their light values: they are mid-tone and read on both. */\n --ui-status-attention-bg: rgba(224, 162, 58, 0.14);\n --ui-status-attention-fg: #e9b862;\n --ui-status-positive-bg: rgba(58, 166, 107, 0.15);\n --ui-status-positive-fg: #6fcf98;\n --ui-status-progress-bg: rgba(123, 111, 224, 0.16);\n --ui-status-progress-fg: #a99ff0;\n --ui-status-inactive-bg: rgba(255, 255, 255, 0.06);\n --ui-status-inactive-fg: #9aa0a6;\n --ui-status-neutral-bg: rgba(255, 255, 255, 0.06);\n --ui-status-neutral-fg: #c3c8cd;\n --ui-status-info-bg: rgba(76, 157, 255, 0.14);\n --ui-status-info-fg: #8ec0ff;\n --ui-status-negative-bg: rgba(240, 121, 79, 0.13);\n --ui-status-negative-fg: #f0a08a;\n --ui-status-paused-bg: rgba(255, 255, 255, 0.05);\n --ui-status-paused-fg: #9a9a94;\n\n --ui-level-high: #f0794f;\n --ui-level-mid: #d6d9dc;\n --ui-level-low: #6b7177;\n --ui-meter-track: rgba(255, 255, 255, 0.1);\n\n --ui-avatar-0-bg: rgba(91, 120, 230, 0.16); --ui-avatar-0-fg: #a3b5f5;\n --ui-avatar-1-bg: rgba(58, 166, 107, 0.15); --ui-avatar-1-fg: #7fd3a4;\n --ui-avatar-2-bg: rgba(224, 150, 58, 0.15); --ui-avatar-2-fg: #ecb36f;\n --ui-avatar-3-bg: rgba(190, 90, 170, 0.15); --ui-avatar-3-fg: #e29dd1;\n --ui-avatar-4-bg: rgba(60, 160, 185, 0.15); --ui-avatar-4-fg: #85cfe0;\n --ui-avatar-5-bg: rgba(160, 145, 120, 0.15); --ui-avatar-5-fg: #cbbfa8;\n --ui-avatar-6-bg: rgba(123, 111, 224, 0.16); --ui-avatar-6-fg: #a99ff0;\n --ui-avatar-7-bg: rgba(220, 90, 90, 0.15); --ui-avatar-7-fg: #f0a0a0;\n\n --ui-viz: #4c9dff;\n --ui-viz-1: #2f5f99;\n --ui-viz-2: #4c8fe6;\n --ui-viz-3: #8ec0ff;\n --ui-viz-rest: #3a3e44;\n --ui-viz-track: rgba(255, 255, 255, 0.05);\n --ui-viz-grid: rgba(255, 255, 255, 0.07);\n --ui-on-viz: #0f1012;\n\n /* ── States (dark) ────────────────────────────────────────────────── */\n --ui-hover-area: rgba(255, 255, 255, 0.03);\n --ui-hover: rgba(255, 255, 255, 0.05);\n --ui-selected: rgba(255, 255, 255, 0.07);\n\n /* ── Shadows (dark): the edge turns into a white hairline. ────────── */\n --ui-shadow-control: 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(255, 255, 255, 0.09);\n --ui-shadow-card: 0 0 0 1px rgba(255, 255, 255, 0.07), 0 1px 2px rgba(0, 0, 0, 0.3);\n --ui-shadow-card-hover: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 2px 6px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.3);\n --ui-shadow-surface: 0 0 0 1px rgba(255, 255, 255, 0.08), 0 2px 6px rgba(0, 0, 0, 0.3), 0 4px 42px rgba(0, 0, 0, 0.35);\n --ui-shadow-surface-hover: 0 0 0 1px rgba(255, 255, 255, 0.1), 0 2px 6px rgba(0, 0, 0, 0.35), 0 6px 42px rgba(0, 0, 0, 0.4);\n /* The button, tooltip and avatar shadows keep their light values. */\n}\n\n/* ── Derived tokens ──────────────────────────────────────────────────────\n * Built from the roles above, so declared on both selectors: a custom\n * property resolves its var() where it is declared, and `.dark` may sit\n * below :root. */\n:root,\n.dark {\n /* The platform scaffold paints the page with --app-canvas. */\n --app-canvas: var(--ui-canvas);\n\n /* Opaque row tints, so sticky cells keep covering what scrolls under them. */\n --ui-row-hover: color-mix(in srgb, var(--ui-ink) 2.5%, var(--ui-paper));\n --ui-row-active: color-mix(in srgb, var(--ui-ink) 4%, var(--ui-paper));\n --ui-row-open: color-mix(in srgb, var(--ui-ink) 5.5%, var(--ui-paper));\n\n /* Warnings use the attention tone. */\n --ui-warning-bg: var(--ui-status-attention-bg);\n --ui-warning-fg: var(--ui-status-attention-fg);\n --ui-success-bg: var(--ui-status-positive-bg);\n\n /* A brand mark keeps its own colour; defaults to the accent. */\n --ui-brand-mark: var(--ui-accent);\n\n /* Accent tints. */\n --ui-accent-soft: color-mix(in srgb, var(--ui-accent) var(--ui-accent-soft-mix), transparent);\n --ui-accent-line: color-mix(in srgb, var(--ui-accent) var(--ui-accent-line-mix), transparent);\n\n /* Focus: a 3px accent halo; text fields add a 1px accent edge. */\n --ui-ring: 0 0 0 3px color-mix(in srgb, var(--ui-accent) var(--ui-ring-mix), transparent);\n --ui-ring-inset: inset 0 0 0 1px var(--ui-accent-line);\n /* Invalid fields: the same shape in danger. */\n --ui-ring-invalid: inset 0 0 0 1px var(--ui-danger), 0 0 0 3px var(--ui-danger-bg);\n}\n\n/* ── Reduced motion ──────────────────────────────────────────────────────\n * Every transition and animation finishes at once, and loops stop. Add the\n * App's looping indicators (spinners, skeleton pulse, saving dots) to the\n * second rule by their class names. */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 1ms !important;\n animation-iteration-count: 1 !important;\n animation-delay: 0ms !important;\n transition-duration: 1ms !important;\n transition-delay: 0ms !important;\n scroll-behavior: auto !important;\n }\n .ui-spin,\n .ui-skeleton,\n .ui-saving-dot {\n animation: none !important;\n }\n}\n",
59
59
  "designing-frontera-app-experiences/references/motion.md": "# Motion and transitions\n\nMotion tells people where something came from, where it went and that the\nApp heard them. It never decorates, never delays the work, and never plays\ntwice for the same thing.\n\n## One scale\n\nDefine durations, easing, distances and scales once as tokens\n(`assets/motion-tokens.css`) and use nothing else:\n\n| Token | Value | For |\n|---|---|---|\n| stagger | 40 ms | delay between rows entering, capped at six rows |\n| micro | 80 ms | colour and opacity on hover and press |\n| quick | 150 ms | closing, hiding, small state changes |\n| fast | 250 ms | opening popovers, panels and menus |\n| resize | 300 ms | a panel or column changing size |\n| medium | 350 ms | larger surfaces moving |\n| slow | 400 ms | content replacing a skeleton, sheets on phones |\n\nOne easing does almost everything: a smooth ease-out\n(`cubic-bezier(0.22, 1, 0.36, 1)`). Use ease-in-out only for looping states.\n\n## Rules\n\n- **Open slightly slower than close.** A popover opens in 250 ms and closes\n in 150 ms; people wait for what arrives, not for what leaves.\n- **Enter from the source.** A panel grows from its launcher's corner; a\n menu from its trigger; a phone sheet slides in from the edge it belongs to.\n- **Small distances.** Entrances move 4 to 8 px and scale from 0.97 to 0.99.\n A 2 px blur is allowed on a single entering element only. Large travel\n feels slow.\n- **Keep the anchor still.** When a panel grows after the first message, it\n grows upward so the input the person is typing in never moves.\n- **Stagger lists, capped.** Rows enter with a delay of index times the\n stagger token, capped at six, so a long list never takes seconds.\n- **Nothing re-animates on update.** Render with stable keys and never\n re-insert existing elements; re-insertion restarts entrance animations and\n reads as flicker. Prove it by counting animation starts during an update.\n- **Skeletons hand over gently.** Content replaces its skeleton in the same\n slot with a short cross-fade, never a jump.\n- **Reduced motion means none.** Under `prefers-reduced-motion: reduce`,\n durations collapse to zero and looping animations stop, including canvas\n and shader effects.\n- **Pause what cannot be seen.** Animated canvases stop when hidden and idle\n at a low frame rate.\n- **Reuse before adding.** Before writing a keyframe, look for an existing\n one; duplicates drift apart.\n\n## Keep it light\n\nMotion should feel like the references people admire without weighing the\npage down:\n\n- **CSS only.** Transitions and keyframes on tokens. No animation library\n for UI motion: it adds bundle weight for what CSS already does.\n- **Animate `transform` and `opacity`.** They run on the compositor. Animating\n `width`, `height`, `top` or `margin` makes the browser lay out the page each\n frame; keep those for a single element that must resize, never for lists.\n- **Blur is expensive.** Use a small blur only on one element entering (a\n popover, a swapped label), never on every row of a list, and never on\n `backdrop-filter` over large areas.\n- **Cap what moves at once.** Stagger at most six items; the rest appear\n without delay. Nothing animates on scroll by default.\n- **`will-change` sparingly**, set just before an animation and removed\n after, never on every element.\n- **Canvas and shader effects** (an animated mark, a glow) pause when hidden,\n idle at a low frame rate, and stop entirely under reduced motion.\n- **Check it:** record a performance trace of opening, scrolling and a data\n refresh; there should be no long frames and no layout work from animations.\n\n## Long-running work\n\nA button that starts work moves through idle, starting, running and done or\nfailed. Show a short word for the running state (\"Researching…\") with a\ngentle pulse on the affected element, then swap the result in with the same\nentrance as any new content. Never animate a spinner over the whole page.\n",
60
60
  "designing-frontera-app-experiences/references/ui-foundations.md": "# App UI foundations\n\nHow each building block of a Frontera App looks and behaves: the scales, the\ncolour roles, the states and the component specs. These cover components, not\nlayouts, so any layout built from them looks finished.\n\nComponent sizes and placements that read like layout (a panel's width, where\ntoasts appear, a toolbar's height) are defaults for a typical desktop App;\nthe layout is yours to compose.\n\nEvery value here is a **default**. The customer's brand overrides it through\nthe tokens in `ui-tokens.css`: fonts, the accent, the primary fill, the radii\nand the canvas. Components read tokens, never literals, so overriding a token\nrestyles the whole App. Motion names (`quick` 150ms, `fast` 250ms, `medium`\n350ms, `slow` 400ms, `stagger` 40ms, `resize` 300ms) come from the separate\nmotion tokens file.\n\n## Contents\n\n[Use the tokens](#use-the-tokens) · [Type](#type) ·\n[Spacing and density](#spacing-and-density) · [Radii](#radii) ·\n[Lines](#lines) · [Elevation](#elevation) · [Colour roles](#colour-roles) ·\n[States](#states) · [Icons](#icons) · [Components](#components) ·\n[Text practices](#text-practices) · [Rules](#rules)\n\n## Use the tokens\n\n1. Import the platform theme (`globals.css`) first, then `ui-tokens.css`, then\n your component stylesheet.\n2. Put brand overrides in `:root` and `.dark` after `ui-tokens.css`. The\n accent tints, focus rings, opaque row tints and `--app-canvas` derive from\n the role tokens, so a new `--ui-accent` or `--ui-paper` carries through.\n3. Dark mode follows the platform: the host sets `.dark`, and the tokens\n redefine themselves under it. Don't add a second switch.\n4. Feature styles may use `var(--ui-*)` and motion tokens only. No hex, `rgb()`\n or `hsl()` in a component rule, and no type or colour in inline `style`.\n\n## Type\n\n| Role | Token | Default | Use for |\n|---|---|---|---|\n| Text | `--ui-font-text` | `--font-sans` (Inter), features `cv11 ss01 ss03` | Everything that isn't a title |\n| Display | `--ui-font-display` | `--font-heading` (DM Sans), falls back to text | Page, section, dialog and state titles; avatar letters |\n| Mono | `--ui-font-mono` | `--font-mono` | Identifiers people copy, at 12px muted. Never decoration |\n\n| Step | Size | Use for |\n|---|---|---|\n| `2xs` | 11px | Key caps, chart axis ticks |\n| `xs` | 11.5px | Sidebar group labels, menu headers, small field labels |\n| `sm` | 12px | Meta lines, counts, captions, card footers, toast detail |\n| `md` | 12.5px | Column headers, pills, buttons, secondary lines |\n| `base` | 13px | Controls, table cells, property values, menu items |\n| `body` | 13.5px | Page default; text people write and read in editors |\n| `lg` | 14px | Section and card titles; longer reading text |\n| `xl` | 15px | Dialog and empty-state titles |\n| `title` | 16px | The toolbar title |\n| `display-sm` | 20px | A record title in a side panel |\n| `display` | 24px | Headline figures in stat tiles |\n| `display-lg` | 26px | A record title on a full page (28px for a lone figure) |\n\n- **Weights:** 400 for reading text, 500 for anything interactive or labelling\n (buttons, nav, pills, column headers, values), 600 for titles and the one\n number that matters in a row. Nothing heavier.\n- **Line height:** 1.45 base; 1.5–1.6 for paragraphs and clamped card text; 1.2\n for titles; 1.25 for two-line cells (name over role); 1.1–1.15 for figures;\n 1.35 in tooltips.\n- **Letter spacing:** 0 for text. Titles tighten as they grow: 14px −0.008em,\n 15px −0.01em, 16px −0.012em, 20px −0.018em, 22px −0.02em, 24px −0.022em,\n 26px+ −0.024em. A unit after a figure goes back to 0.\n- **Tabular numbers** (`font-variant-numeric: tabular-nums`) for every number\n that sits in a column or changes in place: counts, times, money, scores,\n deltas, axis labels. Headline figures in stat tiles stay proportional.\n- **Truncation:** single-line text ends in an ellipsis; its flex parent needs\n `min-width: 0`. The full value goes in a tooltip or `title`. Clamp multi-line\n text by role: card body 3 lines, secondary card line 2, long table text 5,\n chart column labels 2. A filter value caps at 180px.\n\n## Spacing and density\n\nSpacing steps (`--ui-space-1`…`13`): 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24,\n28, 48px.\n\n- 2px: between nav items, inside segmented tracks, between stacked menu rows.\n- 4–6px: icon to label inside a control, chip to chip, popover inner padding.\n- 8–10px: avatar to name, card inner gap, control horizontal padding.\n- 12–14px: table cell padding, toolbar gaps, card padding, board gaps.\n- 16–20px: section padding in panels, record header padding.\n- 24–48px: empty-state padding, the end of a scrolled page.\n\n| Height | Token | Components |\n|---|---|---|\n| 20px | `--ui-control-2xs` | Count badges, pills inside meta lines, due chips |\n| 22px | `--ui-control-xs` | Pills, tags, inline clear and copy buttons |\n| 24px | `--ui-control-sm` | Card menu button, choice toggles, source links |\n| 28px | `--ui-control-md` | Buttons, segmented tabs |\n| 30px | `--ui-control-lg` | Search, filter chips, icon buttons, back crumb |\n| 32px | `--ui-control-xl` | Nav items, menu items, inputs, in-cell editors |\n| 34px | `--ui-row-header` | Table header, property rows |\n| 44px | `--ui-row` | Table rows, filter bar, side-panel bar |\n| 52px | `--ui-toolbar` | Top toolbar |\n\nDensity is desktop-dense: 44px rows, 32px controls. On a coarse pointer every\ntarget grows to 44px (`--ui-touch-target`) without changing how it looks; see\n[Rules](#rules).\n\n## Radii\n\n| Token | Value | Use for |\n|---|---|---|\n| `--ui-radius-2xs` | 4px | Skeleton bars, checkboxes, swatches |\n| `--ui-radius-xs` | 5px | Key caps, small counts |\n| `--ui-radius-sm` | 6px | Pills, tags, menu items, inline buttons |\n| `--ui-radius-md` | 7px | Buttons, segmented tabs, in-cell editors, tooltips, 24px avatars |\n| `--ui-radius-lg` | 8px | Inputs, search, chips, nav items, icon buttons |\n| `--ui-radius-xl` | 10px | Popovers, cards, text areas, rail items |\n| `--ui-radius-2xl` | 12px | The main surface, board columns, chart cards, state icons |\n| `--ui-radius-3xl` | 14px | Side panels, dialogs |\n| `--ui-radius-full` | 999px | Dots, person avatars, meter tracks |\n\n**Nesting rule:** inner radius = outer radius − the padding between them. A\nsegmented track is 9px around 7px tabs with 2px padding; a 10px popover with\n4px padding holds 6px items. Bigger surfaces get bigger radii, never the\nreverse.\n\n## Lines\n\n- 1px hairlines in `--ui-line` (ink at 8%) divide rows, sections and toolbars.\n- `--ui-line-strong` (ink at 14%) marks an edge that must read: checkbox\n outlines, a chart baseline, scrollbar thumbs, the selected choice.\n- Draw outlines as `box-shadow: inset 0 0 0 1px` so they never shift layout.\n Use `border` only for dividers between rows and sections.\n- 1.5px `line-strong` marks a drop target or a keyboard-active card. A 2px\n inset ink bar on the first cell marks the keyboard-active table row.\n\n## Elevation\n\n| Token | For |\n|---|---|\n| `--ui-shadow-control` | Inputs, search, secondary buttons, active chips, key caps: a 1px inset edge plus a 1px drop |\n| `--ui-shadow-card` | Resting cards, the main surface, the current nav item, the segmented pill |\n| `--ui-shadow-card-hover` | A card under the pointer, open or keyboard-active |\n| `--ui-shadow-surface` | Anything floating: popovers, side panels, dialogs, toasts |\n| `--ui-shadow-button` | The primary button: a soft drop and a 1px top highlight |\n| `--ui-shadow-tip` | Tooltips |\n| `--ui-ring` / `--ui-ring-inset` | Focus: a 3px accent halo, plus a 1px accent edge on text fields |\n\nThe page has two levels: the canvas, and paper surfaces on it. Lift comes\nfrom layered shadows that each start with a 1px edge, so a card needs no\nborder. In dark mode the edge becomes a white hairline and the drops darken.\n\n## Colour roles\n\nNeutral ink on paper. The accent is for links and focus only. Status tints\nstay quiet. The primary action is near-black, not brand-coloured, unless the\nbrand overrides `--ui-primary`.\n\n| Role | Token | Light | Dark | From |\n|---|---|---|---|---|\n| Canvas | `--ui-canvas` | #f6f7f8 | #0f1012 | `--l-soft`, `--app-canvas` |\n| Paper | `--ui-paper` | #ffffff | #16181b | `--l-paper` |\n| Fill (tags, counts) | `--ui-fill` | #eff1f3 | #1a1c1f | `--l-soft-2` |\n| Ink | `--ui-ink` | #101010 | #f2f3f4 | `--l-ink` |\n| Secondary ink | `--ui-ink-secondary` | #2f3337 | #d6d9dc | `--l-ink-2` |\n| Muted ink | `--ui-ink-muted` | #6b7177 | #9aa0a6 | `--l-muted` |\n| Faint ink | `--ui-ink-faint` | #70767c | #868c92 | `--l-faint` |\n| Line | `--ui-line` | ink 8% | white 7% | `--l-line` |\n| Strong line | `--ui-line-strong` | ink 14% | white 12% | `--l-line-strong` |\n| Primary fill | `--ui-primary` | #2a2e33 | #e8eaec | `--l-button` |\n| Accent | `--ui-accent` | #006fe6 | #4c9dff | `--l-blue` |\n| Accent tint / edge | `--ui-accent-soft` / `-line` | 10% / 55% | 14% / 60% | `--l-blue-soft` / `-line` |\n| Tooltip | `--ui-tip` / `--ui-on-tip` | ink / white | inverted | `--l-tip` |\n| Scrim | `--ui-scrim` | ink 12% | black 35% | `--l-scrim` |\n| Danger | `--ui-danger` / `-bg` | #b42318 / #fef3f2 | #f97066 / 10% | `--l-danger`, `--l-overdue` |\n| Success | `--ui-success` | #1d8a4e | #4cc283 | `--l-success` |\n| Warning | `--ui-warning-bg` / `-fg` | the attention tone | | `--l-new-*` as used for warnings |\n\nFaint ink sits just below muted and still passes 4.5:1 on paper. Use it for\nempty values, placeholders and chevrons, never for text people must read.\n\n**Status tones.** Each tone is a triad: a tinted background, a dark foreground\nand a saturated dot for places with no pill. A pill draws its own dot in\n`currentColor` at 85%.\n\n| Tone | Meaning | Light bg / fg | From |\n|---|---|---|---|\n| `attention` | Needs someone; also warnings | #fbf3e4 / #85570b, dot #e0a23a | `new`, `stage-proposal` |\n| `positive` | Good, done, won | #e9f5ee / #1d6b40, dot #3aa66b | `qualified`, `stage-won` |\n| `progress` | In motion | #efedfa / #4a3fa3, dot #7b6fe0 | `contacted`, `stage-design` |\n| `inactive` | Closed out, set aside | #f1f2f3 / #62686e, dot #a7adb3 | `disqualified` |\n| `neutral` | First step, no judgement | #f1f2f3 / #4b5157 | `stage-discovery` |\n| `info` | Waiting on someone else | #e8f1fd / #1d5fb8 | `stage-procurement` |\n| `negative` | Lost, gap, failed | #fdf0ee / #9b3a2a | `stage-lost` |\n| `paused` | On hold | #f4f4f2 / #6b6a64 | `stage-hold` |\n\nMap your domain's values onto these tones with `data-*` attributes. Don't\ninvent new hues per value.\n\n**Other sets:** eight avatar tone pairs (`--ui-avatar-0…7-bg/-fg`); signal\nlevels (`--ui-level-high/-mid/-low`); score meter bands\n(`--ui-meter-good/-fair/-poor` on `--ui-meter-track`); charts use one accent\n(`--ui-viz`), an ordinal ramp (`-1…-3`), a neutral rest, a track and a grid.\n\n**Dark mode:** neutrals flip, and paper (#16181b) sits above the canvas\n(#0f1012). Tints become the tone's hue at 13–16% alpha with a lighter\nforeground. The primary button and tooltip invert to near-white with dark\ntext. Shadow edges become white hairlines over heavier black drops, and the\nscrim darkens to 35%.\n\n## States\n\n| State | How it is drawn |\n|---|---|\n| Hover, control | `--ui-hover` fill (ink 5.5%) and the label moves to ink |\n| Hover, large area | `--ui-hover-area` (ink 3.5%) for header cells, chart columns and list rows |\n| Hover, sticky table row | `--ui-row-hover`, an opaque 2.5% ink mix so sticky cells still cover content |\n| Keyboard-active row | `--ui-row-active` (4%) plus a 2px inset ink bar |\n| Open (shown in a panel) | `--ui-row-open` (5.5%); in a list, `--ui-selected` (7%) |\n| Current nav item | Paper with `shadow-card`: a raised pill, not a colour |\n| Pressed | Buttons scale to 0.97 (`quick`) |\n| Focus | `:focus-visible` only, `--ui-ring`. Text fields show it on their wrapper with `:focus-within` (ring-inset + ring). Rows show it as the active state, not a ring |\n| Disabled | Primary 0.4, icon buttons 0.35, fields 0.55; default cursor; no hover. A read-only editor keeps its value, drops its chevron and explains why in a tooltip |\n| Invalid | `--ui-ring-invalid`: a 1px inset danger edge and a 3px danger-bg halo, with a 3px shake (300ms) |\n| Refused write | The row flashes `--ui-danger-bg` for 30% of 900ms, then fades back; the value reverts |\n\n## Icons\n\nOne outline set (Lucide) at its default 2px stroke. Change the stroke only for\na 3px check inside an 11px checkbox, and a 1.8px drawn check in a success\ntoast. Icons take `currentColor`: muted at rest, ink when hovered or current,\nfaint for chevrons and property labels. Every icon is `aria-hidden`. An\nicon-only button carries an `aria-label` and a tooltip.\n\n| Size | Where |\n|---|---|\n| 16px | Nav items, icon buttons, toasts, back crumb |\n| 15px | Search, segmented tabs, card menu, pending-block icon |\n| 14px | Filter chips, property labels, signal marks, menu checks |\n| 13px | Sort arrows, chevrons, deltas, badge icons, spinners |\n| 12px | Clear and copy buttons, due chips, lock, row \"open\" |\n| 18px | Inside the 40px empty-state tile |\n\n## Components\n\n**Buttons.** Height 28, padding 0 10, radius `md`, 12.5/500, gap 6.\n- *Primary:* `--ui-primary` fill, `--ui-on-primary` text, `shadow-button`;\n hover `--ui-primary-hover`. One per surface, placed last in a group.\n- *Secondary:* paper with `shadow-control`, ink text; hover canvas fill.\n- *Quiet:* no fill, muted text; hover `--ui-hover` and ink.\n- *Icon-only:* 30×30, radius `lg`, 16px muted icon.\n- *Hover-revealed actions* (a row's open button, a card's menu, 24×24 radius\n `sm`): opacity 0, then 1 on row or card hover or `:focus-visible`, sliding\n 2px.\n- A shortcut inside a button is a transparent key cap at 70% opacity.\n\n**Inputs and selects.** Height 32, padding 0 10, radius `lg`, 13px, no border:\npaper and `shadow-control`. Placeholder faint. Focus: ring-inset plus ring.\nLabel above at 12px muted, gap 5–6. Textareas: line height 1.5, padding 8 10;\nan auto-growing note runs from 84px to 320px. Selects are native with\n`appearance: none` and a small muted chevron. An in-cell editor input is 32px\ntall with 6px padding, and shows its ring for as long as it is editing.\n\n**Search.** 30px tall, 240px wide, growing to 320px on focus (`resize`).\nRadius `lg`, paper with `shadow-control`. 15px icon at the left; input\npadding 0 28 0 30. When empty it shows a `/` key cap; when filled, a 22px\nclear button. Esc clears, then blurs. Focus draws on the wrapper.\n\n**Filter chips.** 30px, padding 0 10, radius `lg`, 13/500 secondary ink. Idle:\nno fill, a 14px chevron. With values: paper and `shadow-control`, reading\n\"Label is A, B\" or \"Label is A, +2\". The label is muted, values are ink, and a\n22px clear target sits inside. A \"Clear\" text button (28px, muted) follows the\nchips.\n\n**Tables.**\n- *Header:* sticky, 34px, 12.5/500 muted on paper, hairline below. Each\n header is a full-cell button (padding 0 12) that turns ink on hover.\n- *Sort:* a 13px arrow, hidden at rest, 50% on hover, solid when sorted, and\n rotated for ascending. The sorted header turns ink. Set `aria-sort`.\n- *Rows:* 44px, padding 0 12, hairline, no wrap, ellipsis. Editable cells pad\n 6 and hold a 32px in-cell editor.\n- *Numeric columns* align right in both header and cells, with tabular\n figures.\n- *First column* is sticky, with a 1px line on its right edge. Row\n backgrounds stay opaque.\n- *Behaviour:* fixed layout with a minimum width, so columns never crush.\n New rows fade in, rising 4px, staggered 40ms and capped at 6 rows. No blur\n on rows: animating a filter on many elements makes long lists heavy. See [States](#states) for hover, active, open and the refused-write\n flash.\n- *Compact table inside a card:* 30px header at 12px, 40px rows, padding 10,\n no line under the last row.\n\n**Pills, tags and badges.**\n- *Status pill:* 22px, padding 0 8 0 7, radius `sm`, 12.5/500, a 6px leading\n dot, tone background and foreground. It shrinks to 20px and 12px inside meta\n lines. Key it on its value so a change swaps in.\n- *Tag (neutral value):* 22px, padding 0 7, radius `sm`, 12.5 secondary ink on\n fill, no dot.\n- *Count badge:* 20px, padding 0 7, radius `sm`, 12/500 tabular on fill; 18px\n and radius `xs` inside a segmented tab.\n- *Icon badge* (a confidence level, say): a pill with a 13px icon and gap 5.\n- *Signal mark:* three rising bars in a 14px glyph, lit bars in the level\n colour, unlit at 22%, with the word beside it. Icon-only where space is\n short, with the word in the label and tooltip.\n- *Date chip:* 12px icon and 12px tabular muted text. Today turns ink 500;\n late turns danger.\n\n**Avatars.** Entity tile 24px with radius `md` (20px/6, 40px/11), with a\ndisplay-font letter at 600 (12, 10.5, 18px). The tone comes from a stable hash\nof the name, and a 1px inner ring keeps it crisp. A logo fades in over the\nletter once it loads, inset 4px. The tile never changes size and never shows a\nbroken image. Person avatar: a 22px circle, 10/600 initials, secondary ink on\nfill, inset hairline; transparent when unassigned.\n\n**Cards and panels.**\n- *Card:* padding 10 12, radius `xl`, paper, `shadow-card`, gap 8. Hover:\n `shadow-card-hover`. Open adds an inset strong line; keyboard-active adds a\n 1.5px outer strong line; while dragging it drops to 45% opacity at 0.98\n scale. Body text clamps at 3 lines (12.5/1.5); the footer is 12px muted. The\n whole card is one click target, and inner controls opt back in.\n- *Chart or summary card:* padding 15 18 18, radius `2xl`, `shadow-card`, gap\n 14. A 14/600 display title over a 12px muted note. While refreshing, the\n body dims to 60%.\n- *Stat group:* tiles separated by 1px gaps over the line colour, so dividers\n come free. Each tile has padding 14 16 13, a 12.5/500 muted label, a 24/600\n value, a 13px muted unit, and a 12/500 delta with a 13px arrow (positive or\n danger, by whether up is good).\n- *Inset group:* canvas fill, radius `2xl`, padding 14, for secondary content\n inside a panel. A *pending block* adds a faint diagonal hatch and an inset\n hairline for \"not available yet\".\n- *Property list:* label 13 muted with a 14px faint icon, value 13 ink, 34px\n rows. An editable value shifts −6px so its text aligns with read-only values.\n\n**Popovers and menus.** Min 208px, max 320px, padding 4, radius `xl`, paper,\n`shadow-surface`, offset 4, collision padding 8. They open from the trigger's\norigin, scale 0.97→1 with a fade (`fast`), and close to 0.99 (`quick`).\n- Header: 11.5/500 faint. Separators bleed to the popover edge.\n- Items: 32px, padding 0 8, radius `sm`, 13 secondary ink. One highlight,\n shared by pointer and keyboard: `--ui-hover` and ink.\n- Single choice: a 14px check at the right. Multiple: a 15px box (radius 4,\n strong inset line) that fills with `--ui-primary` as its check scales in.\n- Counts sit at the right, 12 faint tabular. Lists scroll after 280px.\n- Keys: arrows or J/K, Home/End, Enter/Space, and a letter to jump.\n\n**Tooltips.** Padding 5 8, radius `md`, inverted, 12/500, line height 1.35,\nmax 280px, offset 6. They fade in after a short delay at 0.98 scale and\nvanish at once. Use them for the exact value behind a compact one, a shortcut,\na disabled control's reason, or what a badge means.\n\n**Dialogs.** A full-screen scrim fades in. The dialog is 400px (600px wide\nvariant), max height viewport − 32px, padding 18 18 16, radius `3xl`,\n`shadow-surface`, with popover motion. Title 15/600 display; body 13/1.5\nmuted with the object's name in ink 600. Actions align right with gap 6, the\nprimary last, 18px below the body.\n\n**Side panel.** Inset 8px from the viewport, `min(520px, 100vw − 16px)`\nwide, radius `3xl`, `shadow-surface`. It enters with a 24px slide, fade and\nblur (`slow`) and leaves at `medium`. Its bar is 44px with a hairline: a\nmuted kind label, faint key hints, then icon buttons. Changing the record\ncross-fades the content.\n\n**Segmented control.** Track padding 2, radius 9, canvas fill and inset\nhairline, gap 2. Tabs are 28px, padding 0 10, radius `md`, 13/500 muted, with\n15px icons; the selected tab turns ink. One paper pill with `shadow-card`\nslides under the selection (transform and width, `fast`). It doesn't animate\non first placement or on window resize. Use `role=\"tablist\"`.\n\n**Sidebar items.** 32px, padding 0 8, radius `lg`, gap 10, 500 secondary ink,\n16px muted icon, 12/500 tabular count at the right. Hover: `--ui-hover`.\nCurrent: the raised paper pill, with the icon in ink. Group labels are\n11.5/500 muted, padding 14 8 6, sentence case. A dot variant swaps the icon\nfor a 7px tone dot.\n*Collapsed rail* (60px column, also forced at narrow widths): items become\n36×36 with radius `xl`, centred. Labels and counts hide. Group labels go\ninvisible but keep 18px of rhythm. Each item gets a right-side tooltip with\nits label, count and shortcut. The width animates at `resize`.\n\n**Toolbars.** 52px, padding 0 12 0 18, gap 12. Title 16/600 display. Crumbs\nuse a faint separator, with the parent in 500 muted. Then a count badge, a\nspacer, a \"Refreshing\" status (12px muted with a 13px spinner), then view\ncontrols. A filter bar below is at least 44px, padding 6 12 6 14, gap 6,\nhairlines above and below, and wraps.\n\n**Skeletons and the reveal.** Bars 9px tall (7px secondary, 8px header) with\nradius 4; pills and avatars use radius 7. Fill `--ui-skeleton`, pulsing 1.4s\nfrom 1 to 0.55. Shape the skeleton like the content: same row height, same\ncolumns, an avatar with two lines, pill-shaped pills. It is `role=\"status\"`\nwith a label. On load the skeleton stays in place and cross-fades out with a\nblur (`slow`) while the content fades in, then unmounts after 400ms. A\nbackground refetch never brings the skeleton back: dim the content or show\n\"Refreshing\".\n\n**Empty, error and no-access states.** Centred in the content's slot, max\n380px. A 40px paper tile (radius `2xl`, `shadow-card`) holds an 18px icon:\nmuted, or danger for an error. Below it: a 15/600 title, a 13/1.55 muted body,\nand at most one button 10px below. It rises 8px with a fade (`slow`). An error\nhas `role=\"alert\"`. No access says who can grant it. A filtered-empty view\noffers to clear search and filters; a truly empty one says what will fill it.\nSmall empties inside a column are 12.5 faint in a 72px box with an inset\nhairline. A value the reader's access excludes shows a 12px lock and\n\"Withheld\" in faint ink, with a tooltip. It never appears as missing.\n\n**Toasts.** Bottom centre, 16px up, newest at the bottom, at most 3, gap 8.\nEach is at least 40px, padding 8 8 8 12, radius 11, paper, `shadow-surface`,\n13px. A 16px icon: a spinner while pending, a check that draws itself on\nsuccess, an alert on refusal, a clock while awaiting approval. The title is\n\"**Object** · change\"; a 12px muted line gives the reason; the phase word sits\nat the right (12/500, coloured); then a dismiss button. Success leaves after\n2.4s; a refusal stays 7s. Toasts rise 16px from 0.97 scale with a blur\n(`medium`) and leave at `fast`. When a side panel is open they centre over the\nremaining content. Use `aria-live=\"polite\"`.\n\n**Progress and score bars.**\n- *Bar list:* 13px secondary-ink label, 13/500 tabular value, and a 6px bar on\n `--ui-viz-track`. The bar is square at the axis and rounded at the end.\n Bars grow in with a staggered clip; hovering a row dims the others to 50%.\n- *Meter ring:* 16px with a 2.25px stroke (4px from 32px up) and round caps,\n coloured by band (good ≥70, fair ≥40, poor), with the number beside it.\n Unscored shows a faint dash and a tooltip.\n- *Composition bar:* 8px tall, full radius, with 3px gaps between segments.\n\n**Saving indicator.** Show saving on the control being edited, not the page. A\n6px muted dot at the right of an in-cell editor replaces its chevron and\nbreathes (1.2s, 0.35↔1). A card shows the dot at top right. A text editor\nshows the phase word (\"Saving\", \"Saved\", \"Not saved\"). The toast summarises.\nNo spinners in cells, and the row stays usable.\n\n**Keyboard hints.** Key cap: 18px tall, min-width 18, padding 0 5, radius\n`xs`, 11/500 muted, in the text font, on paper with `shadow-control`. Put them\nin tooltips (\"Close Esc\"), in search (`/`), in nav tooltips, inside buttons\n(⌘ Enter), and in a footer legend of keys plus a lowercase verb (\"J K move\").\n\n**Links.** Accent, no underline; on hover, an underline at 40% accent, offset\n3px.\n\n## Text practices\n\n- **Sentence case everywhere:** nav, buttons, headers, labels, group labels.\n No all-caps eyebrows. Sidebar group labels and menu headers are small and\n muted, not uppercase.\n- **Numbers:** grouped counts (\"1,234\"), with \"1,234+\" when a list is capped.\n Money is compact in tiles and cards (\"$1.2M\", \"$558K\", \"$900\"), with the\n exact amount (\"$558,000\") on hover and in tables. Percentages are whole.\n Durations are compact (\"45m\", \"5h\", \"2d 4h\", \"12d\"); quarters read \"Q4 2026\".\n- **Dates:** relative in dense rows (\"Just now\", \"12m ago\", \"3h ago\",\n \"Yesterday\", \"4d ago\", then \"12 Oct\"). The year shows only when it isn't\n this one. The exact date, time and time zone go in the tooltip.\n- **Empty values:** an em dash in faint ink. Name the absence when it means\n something: \"Unassigned\", \"No value\", \"Not given\", \"Not set\", \"Not scored\n yet\". Withheld is not empty. An unknown enum value reads as itself in\n sentence case rather than disappearing.\n- **Copy module:** every visible string lives in one `copy` object, grouped by\n surface, `as const`. Interpolated and plural strings are functions\n (`count => \\`${count} more\\``). Enum labels live in label maps beside the\n formatters. Typographic quotes and apostrophes (’ “ ”), and \"…\" for work in\n progress.\n- **Don't over-explain.** A title says what happened; the body gives the next\n step. Keep the qualifiers that change meaning: the time window (\"Last 30\n days\", \"vs prior 30d\"), scope (\"All time\"), denominators (\"3 of 12\"),\n uncertainty (\"Checking\", \"Awaiting approval\"), and the reason a control is\n read-only. Drop \"successfully\", restated labels and instructions for\n obvious controls. An error names what failed and what the App did about it\n (\"We put the previous value back\").\n\n## Rules\n\n1. No literal colours in feature styles. Colour and shadow come from tokens;\n type, radius and spacing come from the scales above.\n2. Motion uses the motion tokens. Under `prefers-reduced-motion: reduce`,\n every transition and animation collapses to 1ms, and loops stop: spinners,\n skeleton pulse and saving dots. The token file does this.\n3. Focus is `:focus-visible` only, with one ring per control.\n4. On coarse pointers (`@media (pointer: coarse)`), every target is at least\n 44px: grow the hit area with padding or a pseudo-element, not the visual.\n5. Keep the platform's scaffolded primitives and your App components on the\n same tokens, or use only one set.\n\n**Where the reference App broke its own rules.** Fix these in new Apps.\n- Inline `style` carried type and spacing in several components (12px and\n 12.5px sizes, weight 500, ad-hoc margins), outside the scale.\n- Some strings skipped the copy module: a row's \"Open\", the shortcut verbs,\n a \"Layout\" label, \"{label} priority\", a pluralised tooltip, a literal \"—\"\n in stat tiles while the copy module has `empty`, and a literal \"⌘ Enter\".\n- Sizes, radii and spacing were px literals in every rule, with no tokens.\n Only colour, shadow and motion were tokenised.\n- Near-duplicate reds (danger, overdue, high level, poor band, negative tone)\n and greens (success, positive fg, good band). A good delta used the\n positive status colour instead of success.\n- Three different disabled opacities (0.35, 0.4, 0.55).\n- Faint and muted ink are nearly identical in light mode.\n- No coarse-pointer sizing: clear, copy and menu targets are 22–24px.\n- A focus ring replaces a secondary button's `shadow-control` edge (same\n property) instead of adding to it, as inputs do.\n- The dialog scrim (12%) contradicts the platform's single 60% scrim.\n- The scaffolded primitives (36px buttons, platform `--primary` and\n `--ring`) went unused and would render a second look if used.\n- One formatter's doc says \"12m\" while it returns \"12m ago\".\n",
61
- "designing-frontera-app-experiences/references/verification.md": "# Verifying what people will see\n\nA passing build says the code compiles and its tests pass. It says nothing\nabout whether the screen is right. Verify the screen the way the person who\nasked will look at it.\n\n## For every visual change\n\n1. Open the App the way people will (`frontera app dev`, or the preview\n deployment), in a browser the coding host can drive.\n2. Set the viewport: 1440 px wide, then 390 px. Add a laptop width (about\n 1024 px) when tables or rails are involved.\n3. Reach the state: loading, empty, error, full, long text, no access. Seed\n data for the awkward ones rather than skipping them.\n4. Screenshot each state in light and dark. Keep the screenshots as evidence\n for the change request.\n5. When the request was about size or shape, measure it (bounding boxes,\n insets, radii) and report the numbers.\n6. When the request was about behaviour, click through it: open, act, close,\n keyboard path, Escape.\n7. Read the console and network: zero errors, zero failed requests.\n8. Reset the viewport and theme when done.\n\n## When the browser disagrees with the layout\n\nIf a screenshot is blank but the layout says the element exists, test the\ntool before the code: place a plain coloured box and screenshot it. Hidden\nor background tabs may not repaint. Switch to a browser that renders, and\nsay which one produced the evidence.\n\n## Motion\n\n- Count animation starts during an update; an update should start none.\n- Check reduced motion by emulating the setting and confirming nothing moves.\n\n## Phones\n\n- Use a simulated narrow viewport, and simulate the on-screen keyboard by\n shrinking the visual viewport; check the input stays visible.\n- Say clearly that a real device was not tested when it was not.\n\n## Reporting\n\nReport separately: code checks (`bun run check`), rendered review (which\nstates, widths and themes), measurements, and what the person has accepted.\nA subagent's \"verified\" is a claim to look at, not an acceptance to relay.\n",
61
+ "designing-frontera-app-experiences/references/verification.md": "# Verifying what people will see\n\nA passing build says the code compiles and its tests pass. It says nothing\nabout whether the screen is right. Verify the screen the way the person who\nasked will look at it.\n\n## For every visual change\n\n1. Open the App the way people will (`frontera app dev --detach`, or the preview\n deployment), in a browser the coding host can drive.\n2. Set the viewport: 1440 px wide, then 390 px. Add a laptop width (about\n 1024 px) when tables or rails are involved.\n3. Reach the state: loading, empty, error, full, long text, no access. Seed\n data for the awkward ones rather than skipping them.\n4. Screenshot each state in light and dark. Keep the screenshots as evidence\n for the change request.\n5. When the request was about size or shape, measure it (bounding boxes,\n insets, radii) and report the numbers.\n6. When the request was about behaviour, click through it: open, act, close,\n keyboard path, Escape.\n7. Read the console and network: zero errors, zero failed requests.\n8. Reset the viewport and theme when done.\n\n## When the browser disagrees with the layout\n\nIf a screenshot is blank but the layout says the element exists, test the\ntool before the code: place a plain coloured box and screenshot it. Hidden\nor background tabs may not repaint. Switch to a browser that renders, and\nsay which one produced the evidence.\n\n## Motion\n\n- Count animation starts during an update; an update should start none.\n- Check reduced motion by emulating the setting and confirming nothing moves.\n\n## Phones\n\n- Use a simulated narrow viewport, and simulate the on-screen keyboard by\n shrinking the visual viewport; check the input stays visible.\n- Say clearly that a real device was not tested when it was not.\n\n## Reporting\n\nReport separately: code checks (`bun run check`), rendered review (which\nstates, widths and themes), measurements, and what the person has accepted.\nA subagent's \"verified\" is a claim to look at, not an acceptance to relay.\n",
62
62
  "designing-frontera-app-experiences/references/visual-craft.md": "# Visual craft for operational screens\n\nOperational screens are scanned, not read. Every visual decision either\nhelps someone find what needs attention or gets in the way.\n\n## Hierarchy\n\n- One question per screen; the answer is the largest, highest thing on it.\n- Group by what people decide, not by how the data is stored.\n- Use weight and size for hierarchy before colour. Colour is for state.\n- Cards only for things that are separate objects. A border, a fill and a\n shadow on every block flatten the page.\n\n## Colour\n\n- **Tokens for everything:** surface, ink, faint ink, line, accent, focus\n ring, shadows, and a background/foreground/dot triad per status. Dark\n values sit beside the light ones.\n- **Status colours are semantic** (new, in progress, won, lost, refused) and\n separate from the accent.\n- **Vivid at reduced opacity.** For categorical parts of a whole, vivid hues\n at about 70% opacity on tracks tinted at about 14%, ordered around the\n colour wheel so the bar reads as a spectrum. Muted tones look dated next to\n a modern accent.\n- **Neutral controls.** Secondary buttons and chips in greys; keep the brand\n colour for links, focus and the one primary action.\n- Check contrast in both themes. A validator proves legibility; compare\n against the product's own accent to judge whether it looks current.\n\n## Data display\n\n- Numbers in tabular figures, right-aligned in columns.\n- Bars and progress fills change `width`, not `transform: scale`, so rounded\n ends stay round.\n- Charts: one scale for marks, ticks and labels; labels name values the chart\n reaches; chart text uses theme tokens.\n- Counts and totals come from aggregate queries or metrics, never from\n counting the rows a page happens to hold. When a view reads a whole set,\n cap it and say when it is partial.\n- Confidential fields a reader cannot see are shown as hidden, never as\n empty values that look like missing data.\n\n## Copy\n\n- Labels are nouns people use; buttons say exactly what happens (\"Enrich\",\n then \"Researching…\", then \"Enriched\").\n- No method explanations, no descriptions of what the team will do with the\n data. Keep only what says what is counted.\n- Errors say what happened and what to do, without apology.\n- Empty states: a few words and the next action.\n\n## Shape and depth\n\n- Shadows: a hairline ring plus two soft layers. No heavy drop shadows.\n- Nested radii: an inner radius equals the outer radius minus the gap,\n unless the gap is about the radius itself.\n- Square, equal hit targets in rails and toolbars; at least 44 px on touch.\n\n## Typography\n\n- Use the product's body and heading faces as its brand defines them; do not\n swap a heading face into body text.\n- Don't download a font the host page already loads.\n",
63
63
  "designing-frontera-app-experiences/SKILL.md": "---\nname: designing-frontera-app-experiences\ndescription: Use when designing or polishing a Frontera App's screens — hierarchy, copy, colour, data display, states, motion and transitions — or when a screen works but doesn't feel right.\n---\n\n# Designing Frontera App experiences\n\nA Frontera App is an **operational interface**: the same people open it\nevery day to do one job. It earns that by showing what needs attention first,\nsaying no more than it must, responding instantly, and never surprising\nanyone. This skill is the design bar. The App's own skills\n(`using-frontera-app-patterns` and the skills it routes to) carry the code\nstructure, and `authoring-frontera-apps` carries the build and deploy loop.\n\nLoad `using-frontera` first. Before designing:\n\n```bash\nfrontera auth current --json\nfrontera blueprint list\n```\n\nDesign from what the workspace is granted: an App sees exactly the object\ntypes, fields and metrics its caller can read. A confidential field shows as\nempty to a reader without sensitive access; design that state on purpose.\n\n## Start with the job\n\nFill `assets/app-design-brief.md`: who opens it, how often, the first\nquestion they bring (\"what is new since this morning?\"), the decisions they\nmake on it, and the Actions those decisions go through. Each screen answers\none question. If a screen answers none, it is a report nobody asked for.\n\n## Hierarchy and layout\n\n- **Summary before detail.** Counts and states that need attention first,\n then the list, then the record. A person should know whether to act within\n a second.\n- **State in form, not only in words.** A pill, a dot or a coloured stripe\n for status, so a column can be scanned without reading it.\n- **One primary action per screen**, placed where every section can reach it\n (for creating, the sidebar with its keyboard shortcut). Never two\n competing buttons for the same thing.\n- **Navigation lives in the URL**: section, filters and the open record, so\n back and forward work and a link can be shared.\n- **Keyboard for daily users:** move through rows, open, close and search\n without the mouse.\n- **Collapsed rails are square and equal**: the same hit target for every\n item, centred, measured rather than eyeballed.\n- **No horizontal scroll at laptop widths.** Optional columns step aside;\n wide content scrolls inside its own container.\n\n## Components and foundations\n\nLayouts are yours to compose; the building blocks should look the same in\nevery App. Start from `assets/ui-tokens.css` (type, spacing, radii, lines,\nshadows, colour roles, states) and `assets/motion-tokens.css`, imported after\nthe platform theme and before your own styles. The customer's brand\noverrides tokens, never component rules. `references/ui-foundations.md` gives\neach scale with when to use each step, and specs for the components:\n\n- **Type:** a dense desktop scale (11 to 26 px) with weights no heavier than\n 600, titles tightening their letter spacing as they grow, and tabular\n figures for every number in a column.\n- **Density:** 44 px rows, 28 to 32 px controls, spacing on a 2 px-based\n scale; targets grow to 44 px on touch without changing how they look.\n- **Depth:** cards lifted by layered shadows that start with a 1 px edge, not\n borders; outlines drawn as inset shadows so nothing shifts.\n- **Colour:** ink on paper over a soft canvas; the accent only for links and\n focus; a near-black primary unless the brand says otherwise; status as quiet\n tone triads (background, foreground, dot) mapped from your values, never a\n new hue per value.\n- **States:** one hover fill, `:focus-visible` rings only, one disabled\n opacity, refused writes flashing then reverting, saving shown on the\n control being edited.\n- **Components:** buttons, inputs, search, filter chips, tables, pills and\n tags, avatars, cards, popovers and menus, tooltips, dialogs, side panels,\n segmented controls, sidebar items and the collapsed rail, toolbars,\n skeletons, empty and error states, toasts, bars and meters, key hints.\n\nUse the platform's scaffolded primitives or your own components, on the same\ntokens, never both with different looks.\n\n## Copy\n\n- **Don't over-explain.** Remove method notes and descriptions of what the\n team does with the data. Keep only qualifiers that say what is counted\n (\"All time\", \"Last 16 weeks\", \"Since 21 Oct\").\n- **Name things the way the people using it do**, never the way the system\n stores them.\n- **Every string in the copy module**, including empty, error and no-access\n text. Empty states in a few words, with the next action.\n- **Internal records look internal.** Prefix internal object types'\n display names (\"[Internal] Activity\") and give each type its own icon and\n colour, so they stand apart in a shared model.\n\n## Colour and data display\n\nRead `references/visual-craft.md`. The rules people notice first:\n\n- **The customer's brand first.** Without one, the default is vivid hues at\n reduced opacity rather than muted tones. Muted earth tones read\n as muddy next to a modern product. Judge against the product's own accent;\n a contrast checker proves legibility, not appeal.\n- **Tokens only.** Colours, shadows, radii and motion come from tokens, with\n dark-mode values beside them. No literal colours in feature styles. Add\n the App's own tokens to the App's stylesheet, never to a generated theme\n file.\n- **Bars size by width, not scale**, so rounded ends stay round.\n- **Aggregate on the server.** Counts come from metrics and aggregate\n queries. When a view must read a whole set, cap it and say when it shows a\n partial set.\n\n## Motion\n\nRead `references/motion.md` and start from `assets/motion-tokens.css`. In\nshort: one scale of durations and one easing; things open slightly slower\nthan they close; enter from where they come from; stagger rows, capped;\nnothing moves for people who ask for reduced motion; nothing re-animates\nwhen data refreshes. Keep it light: CSS only, `transform` and `opacity`,\nblur on single elements only, no animation libraries.\n\n## Loading, empty, error and writes\n\n- **Skeletons shaped like the content**, in the content's slot, cross-fading\n out when data arrives. No spinners for page content.\n- **Background refreshes dim, never blank.** Keep the previous data while\n fetching.\n- **Writes are optimistic and honest:** show the change at once, submit the\n Action, follow it to its outcome (applying, saved, or refused with the\n reason), and revert visibly when refused. Serialize writes per record so\n the second reads the first's version.\n- **Long-running actions are a state machine:** idle, starting, running (with\n a word for what is happening), done or failed. Re-read the record until the\n result appears. Map each refusal to a sentence a person understands.\n- **Never add UI the solution cannot honour.** A \"Book a meeting\" button\n with no booking behind it, a disclaimer nobody asked for, a filter the data\n cannot answer: leave them out.\n\n## Changing an existing screen\n\n- **Change only what was asked.** Asked to round one corner, round one\n corner. Retuning neighbours \"for consistency\" is a separate proposal.\n- **Before reversing a working design, confirm the scope in one line.**\n- **When matching a reference, measure it** (insets, radius, height,\n timings) in a browser, and restate the geometry before building.\n- **Public code carries no reference-site or competitor names**, in comments\n or in built files.\n\n## Verify what people will see\n\nRead `references/verification.md`. A visual change is done when you have\nlooked at it, not when the build passes:\n\n- every state (loading, empty, error, full, long text, no access);\n- at 1440 px and 390 px, in light and dark;\n- measured when the request was about size or shape;\n- clicked through when the request was about behaviour;\n- zero console errors and zero failed requests.\n\n`bun run check` in the App proves the code. Screenshots prove the screen.\nReport them separately, and never relay a subagent's \"verified\" as accepted\nwithout looking against the person's taste rules.\n\n## Before you report done\n\nRun the review in `reviewing-frontera-work` before reporting any screen done, with its\n`rubric-apps-and-embeds.md` rubric. When the person corrects you, record the\ncorrection as that skill describes, so it is applied next time\nwithout being asked.\n\n## Red flags\n\n- A screen with no question it answers.\n- Explanatory text under every heading.\n- A literal colour, a one-off duration or a new keyframe that duplicates an\n existing one.\n- A list that re-animates every row on refresh.\n- A button that leads nowhere yet.\n- \"Looks good\" with no screenshot at phone width.\n\n## Assets and references\n\n| File | Use it for |\n|---|---|\n| `assets/app-design-brief.md` | who, the job, the first question, the decisions and Actions |\n| `assets/ui-tokens.css` | type, spacing, radii, lines, shadows, colour roles and states, light and dark |\n| `assets/motion-tokens.css` | a complete motion scale with reduced-motion handling |\n| `assets/screen-review-checklist.md` | the review every screen passes before it ships |\n| `references/ui-foundations.md` | every scale and component spec, text practices, and the rules |\n| `references/visual-craft.md` | hierarchy, colour, data display, copy |\n| `references/motion.md` | durations, easing, entrances, what never animates |\n| `references/verification.md` | how to look at a screen and prove it |\n",
64
64
  "designing-frontera-blueprint/agents/openai.yaml": "# Codex presentation metadata. Host-specific by design: SKILL.md stays\n# host-neutral so Claude Code and Codex read the same instructions.\ninterface:\n display_name: Designing a Frontera Blueprint\n short_description: Decide what the shared model should be, or review one somebody wrote.\n default_prompt: Model our business so agents can actually use it.\npolicy:\n allow_implicit_invocation: true\n",
@@ -71,7 +71,7 @@
71
71
  "designing-frontera-blueprint/references/platform-and-data.md": "# Frontera's platform answers, and the data foundation\n\n## Part 1 — What Frontera actually gives you\n\nThe upstream version of this file asks eight platform questions abstractly,\nbecause it is written for any object-graph platform. Here the platform is known,\nso the answers are given. **Still write them into the design as a list** — a\nreviewer should be able to falsify a premise at a glance, and a premise that has\nchanged since this file was written is exactly what you want them to catch.\n\nWhere this file and the CLI's generated help disagree, the CLI wins. It is\ngenerated from the command table; this is prose.\n\n### 1. Primary key — single-property\n\nA Blueprint object type identifies an instance by **one** primary key drawn from\na column of its backing model. Composite grain therefore needs a deterministic\nhash produced upstream, e.g. `sha256(concat_ws('|', period, entity, party,\nproduct))` materialised as `<type>Key`. Record the grain columns in the design:\na hash whose inputs are undocumented is unmaintainable.\n\n**Never an ETL surrogate.** A key that changes on reload loses edits, breaks\nlinks, and invalidates every saved query and every citation an agent ever gave.\nThis is one of the three things that cannot be fixed later.\n\n### 2. Traversal — links with declared cardinality, walked by agents\n\nLink types carry a cardinality, the explorer walks them, and a granted agent\ntraverses them when a question spans types. Three hops is the working ceiling and\nis roughly where retrieval quality holds up anyway; set `meta.traversal_limit` in\n`model.yaml` so the linter checks against a number you decided rather than an\nassumption.\n\n**What changes:** impact questions (\"who is affected by this outage\") and\n360-degree questions (\"everything about this counterparty\") are naturally four or\nfive hops. They need shortcut links over bridge tables, plus precomputed counts.\n\n### 3. Metrics — yes, they filter, dimension and bucket time\n\nFrontera metrics are named measures bound to a metrics view in the analytics\nconnection. Agents query them with a **time grain, dimensions and filters**, and\nseveral in one call. So the expressive column of the classic trade-off applies:\n\n- revenue can be a filtered measure rather than a signed split column;\n- ratios can be named measures rather than a numerator and denominator the\n consumer divides;\n- a restatement predicate can live in the metric.\n\n**The one thing that does not follow:** a dimension has to exist on the bound\nmetrics view. A filter an executive will reach for is still a materialisation\ndecision upstream — it is just made in the view rather than on the object type.\n\nPromote a number to a metric when it is **quoted across contexts**. If it is\ncomputed ad hoc in three places, those three places will eventually disagree.\n\n### 4. Derived properties — same-object\n\nBlueprint properties are typed fields mapped from the backing model's columns.\nThere is no link-traversing derived property, so **every cross-object value\nbecomes a mart column**.\n\nHandle it the way the platform cannot: register all of them in one table with\nthe source id alongside, give the mart sole write authority, and add\nreconciliation checks — each copy is drift surface. Then say plainly in the\ndesign that this is a knowing deviation from \"store each fact once\", and why.\n\n### 5. Interfaces — not available; use a naming contract and groups\n\nThere are no capability interfaces to declare conformance against. The\nsubstitutes are a **naming contract enforced by review** (`siteId` always means\nthis; `<x>From`/`<x>To` always means that) and the free-form **group** labels\nthat organize the registry.\n\n**Specify the interfaces anyway**, with a conformance column per implementer.\nWriting the spec is what surfaces the drift, and it makes an eventual migration\nmechanical rather than a redesign. Expect to skip Actions that would have to be\nbuilt once per implementing type — building one seven times repeats the same\nshape six times too many.\n\n### 6. Actions — authored, typed, reviewed\n\nFrontera has real authored Actions: a portable Definition carrying subject,\ntyped inputs, outcomes, concurrency, impact and approval, deployed through a\nreviewed Binding onto exactly one target. Specify each properly and let the\nplatform enforce.\n\nTwo constraints to design around:\n\n- **One Binding, one effect boundary.** No fan-out, no target chosen from\n invocation data, no runtime fallback. A cross-system process is an\n orchestrated sequence of separate Actions, each with its own certainty and\n compensation policy. \"Close the incident and resolve its linked breaches\" is\n two Actions and a consistency check, not one transaction.\n- **Approval belongs to the Definition.** An Action that declares approval\n cannot be talked out of it by a caller. Decide it when you design the Action,\n not when you wire the consumer.\n\nThe platform records a request, approval, attempt and outcome ledger of its own.\nProject what the business needs to *query* — which Action, which actor, human or\nagent, when, against which object, prior and new value, and the amount at stake\n— into the model as an object type. That is what turns \"who waived this, when,\nand against what exposure\" into an aggregation rather than a support ticket.\n\n### 7. Security — object-type grants, two-dimensional\n\nAccess is granted per **workspace** and per **agent**, at object-type\ngranularity. There are no row-level or column-level policies.\n\nA workspace granted a type therefore sees **every row of it**. Where a model\nspans legal entities, joint ventures or a regulated separation, disclose this\nexplicitly — \"the scoping is advisory, not enforced\" — and contain it by\ngranting sensitive types to as few workspaces as possible.\n\n**Do not fork the object type per entity.** That is the anti-pattern; it\nfragments the canonical type the whole design exists to create.\n\n### 8. Writeback — a governed overlay, with the ledger\n\nWrites do not go into the source system by default. A property must be declared\n**editable**, and a governed Action is what writes it; the edit is stored as an\noverlay over the read model, with the Action ledger as its audit trail. The\nauthoritative business record stays where it already lives unless a Binding was\ndeliberately deployed against it.\n\n**Say this to the customer explicitly and early.** The alternative reading —\nthat an agent will write into their billing system — is the one that stops\ndeployments, and it is the reading people default to.\n\nNarrowing the editable set is itself a governed act: an edit already stored\nagainst a property that stops being editable is no longer readable.\n\n## Recording the answers\n\nPut them in the design as a table, note anything you could not verify against\nthe live deployment, and set the two the linter uses:\n\n```yaml\nmeta:\n traversal_limit: 3\n agent_tool_budget: 20\n```\n\nIf an assumption is uncertain, say so and name what changes if it is wrong. That\nsentence is what lets a reviewer catch a bad premise in ten seconds instead of\nafter the model is built around it.\n\n---\n\n## Part 2 — The data foundation\n\nGive an honest readiness verdict early and do not soften it. This is also where the calendar time actually goes, so getting it wrong makes every other estimate wrong.\n\n## The readiness ladder\n\nWalk top-down and stop at the first tier that fits.\n\n### Tier 1 — Ready: connect and go\n\nThe data team has already modelled it. Look for fact tables with dimension tables around them, joining on declared keys. Any recognisable star schema or curated mart qualifies; volume does not matter, because the modelling work is done.\n\n**Next step:** map object types onto the marts and demo the same week.\n\n### Tier 2 — Simple: write projection models\n\nNo marts, but the operational schema is clean and small enough to project with stateless `SELECT` models. All of these must hold:\n\n- Tables have primary keys.\n- Relationships are explicit — declared foreign keys, or join columns that are obvious and consistently named.\n- The volume is projectable — a stateless `SELECT` over it returns in seconds. Judge this by *behaviour*, not by a row count: tens of millions of rows are unremarkable on a modern warehouse and only become a problem when there is no curated layer and no ability to project.\n- **Current state is readable directly** — no replaying events or history to derive it.\n- The schema is tidy: no litter of `tmp_`, `_backup`, `_old`, `_copy`.\n\n**Next step:** scope the projection models and name the tables they read.\n\n### Tier 3 — Not ready: the mart comes first\n\nAny one of these disqualifies:\n\n- **History-dependence.** Current state must be computed rather than read. Signals: validity-window columns, `*_history`, `*_snapshot`, `*_versions`, ledger tables.\n- **Volume that defeats stateless projection** — the operational store cannot serve the query without materialisation, and there is nothing materialising it. **Do not cite a row count as the reason.** Naming a threshold like \"above ten million rows\" will get the whole verdict dismissed by the first competent data engineer who reads it, and they will be right to dismiss it; modern stacks handle that comfortably. The real disqualifier is the absence of a curated layer, not the size of the raw one.\n- **Missing primary keys** across a meaningful share of the schema.\n- **Clutter** — several backup or temp tables and nobody can say which is authoritative.\n- **Sprawl** — a schema large enough that nobody can say which table is authoritative, with no curated layer over it.\n\n**Next step: be direct.** A Tier 3 verdict is a *scope statement, not a rejection*: the first deliverable is a conformed mart, and the Blueprint design is its specification. Say it in those words. Almost nobody in this market will tell a prospect their data is not ready — having a checkable verdict is a differentiator, not an apology.\n\n### Questions that surface the tier fast\n\n- \"Do you have a warehouse or BI layer, or do reports run on production?\"\n- \"If I asked for current customer status, is that one table, or do I replay history?\"\n- \"How many tables, and how big is the largest?\"\n- \"Who owns the schema — a data team, or the application?\"\n\nExpect a multi-entity group after a restructuring to be Tier 3 almost by construction: several source estates, assets that moved between legal entities mid-year, and agreement state that needs validity-window resolution.\n\n**Reason the verdict from the signals that actually disqualify, and say which ones apply.** A verdict of \"Tier 3 because you have 11 million rows\" is wrong and will be dismissed. \"Tier 3 because current state has to be replayed from history, reporting runs off a replica with no curated layer, and three source systems disagree about who a customer is\" is the same verdict, correctly argued, and it survives the room.\n\n**The verdict must agree with the plan.** A six-week schedule printed above a not-ready verdict is a contradiction a reader will find. If the answer is not-ready, the plan starts with the mart and says so.\n\n## The mart contract\n\nOne table or view per object type: current-state, denormalised, with a declared primary key. Write it as a table in the design — mart output, object type it feeds, and **the hard part**, stated honestly.\n\nThe hard part is almost always **a conformed master for the entity that appears in every other type** — the customer, the party, the product, the site. That single dependency usually decides whether the headline question has one answer or several, and it is the thing to name in the first conversation rather than the fifth.\n\nAlso specify:\n\n- **Bridge tables** for every many-to-many link and every shortcut link, by name.\n- **Snapshot tables** for every snapshot object type — without them there is no trend, and no amount of query expressiveness substitutes.\n- **Which precomputes are required**, and which are only there because a platform primitive is missing. The second list should shrink over time; label it so it can.\n\n## Keys\n\n| Pattern | When | Form |\n|---|---|---|\n| Natural business key | The business already has an identifier | Use it verbatim |\n| Deterministic hash | Composite grain | `sha256(concat_ws('\\|', <grain columns>))`, materialised upstream. **Document the grain columns** |\n| Extension mirror key | 1:1 extension of a core type | Reuse the core's key |\n\n**Never an ETL surrogate.** A key that changes on reload loses edits, breaks links and invalidates every saved query and every agent citation. Where a type is restatable, the restatement version goes **inside the hash**, so prior versions survive rather than being overwritten.\n\n## Refresh\n\n- **Dimension-grain types** reload in full. They are small, and correctness beats cleverness.\n- **Fact-grain types** hydrate full-then-incremental on the deterministic key, so a replayed window overwrites rather than duplicates.\n- **Snapshot types** append one partition per period and are never back-edited.\n- **Bitemporal types never overwrite a superseded row** — close the known-to window and insert.\n\n## Budget honestly\n\nRoughly **80% of effort** in a programme of this kind goes into data cleaning, entity resolution and schema mapping — not modelling. A plan that does not reflect that is wrong, and it will become visibly wrong late, which is the expensive way.\n\nSay this at the start. It reframes the modelling work from \"the project\" to \"the specification for the project\", which is both accurate and much easier to deliver against.\n",
72
72
  "designing-frontera-blueprint/references/review-mode.md": "# Review mode — auditing someone else's model\n\nSomeone has written an object model and wants to know what is wrong with it before they build it. This is not the design workflow run backwards. It has different inputs, a different output, and a different audience — the person reading it usually has to carry your findings back to the team that wrote the thing.\n\n## What a good review is\n\n**A defect list they can act on, ordered by cost of being wrong**, grounded in *their* domain rather than in your methodology. Everything else is secondary.\n\nThree failure modes to avoid, all of which independent reviewers have flagged in output produced from this skill:\n\n1. **Grading the model against a process it was never asked to follow.** \"There are no competency questions\" is not a defect in their model; it is an observation about how it was produced. If the absence caused a concrete problem — a type nobody needs, a use case nothing supports — report *that*, and the missing questions as the cause.\n2. **Structural findings crowding out domain findings.** Running the tests is fast and feels productive, and it will find the naming, keying and typing defects. It will not find that they have modelled a reusable shipping container as if it were single-use, or that a business with claims has no cargo-item level to attach a claim to. **Domain defects are the ones that cost money.** Read the model as someone who knows the business, not only as someone who knows modelling.\n3. **Manufacturing volume.** Transcribing their model into your own format so a tool can score it produces impressive-looking output that the client cannot audit and that is, in the end, your own transcription scored against your own rules. Do it only when the model is large enough that mechanical checks will find things reading will not — and when you do, report defect classes rather than instance counts, and say plainly that it is your transcription.\n\n## The workflow\n\n### 1. Read their notes first, then the model\n\nWhatever they wrote around the model — a covering note, comments, \"notes from the team\" — is where the requirements are. Read it before the model itself, and extract:\n\n- **What they say the model must do.** These become the acceptance test. A model that cannot answer its author's own stated requirement is the strongest finding available, and it is entirely fair because they set the bar.\n- **What they already know is wrong.** Teams frequently flag a problem in prose and then model it the way that causes it. \"Some companies are both a customer and a carrier for us; currently they appear twice\" followed by three separate types is a gift: the finding writes itself and nobody can argue it is your preference.\n- **Volumes, systems and constraints** — these drive the readiness verdict.\n\n### 2. Score their model against their own stated requirements\n\nFor each requirement they named: can this model answer it? Where the answer is no, say what is missing. This section usually carries the review, because it is unarguable.\n\n### 3. Run the structural tests\n\nFrom Part 2 of `references/modelling-patterns.md`: the subset test, the ownership test, the instance-stability test, and the anti-pattern catalogue. These are fast. Common finds, roughly in order of frequency:\n\n- Several types for one concept, where one real-world thing has been split by source system — and the reverse, an abstraction that has swallowed the words the business actually uses\n- Joins on names rather than keys\n- Derived values stored as columns — especially percentages, which cannot be aggregated\n- A state modelled as an object type\n- One type conflating several concepts with different lifecycles\n- No history where the requirements need it\n- Free-text where a controlled vocabulary or a reference type belongs\n- Missing currency, unit or timezone on quantities that have them\n\n### 4. Read it as a domain expert\n\nThis is the step that distinguishes a good review. Ask, for this specific business:\n\n- **What is physically or commercially true that the model cannot represent?** Reusable assets modelled as single-use. Multi-party transactions modelled as two-party. Things that can be partially delivered, partially paid, partially cancelled.\n- **What does every business of this kind have that is missing here?** Documents. Addresses of more than one kind. The payable side of anything with a receivable side. Compliance regimes reduced to a single expiry date.\n- **What has one amount that needs several?** A claim with one `amount` that must serve claimed, reserved, settled and recovered. A charge with one `cost` that must serve rated, accrued, invoiced and settled.\n- **Which properties will never be populated?** Assets they do not own, systems they do not run, data that belongs to a counterparty.\n\n### 5. Give the readiness verdict, correctly reasoned\n\nSee `references/platform-and-data.md`. Base it on **curation, history-dependence and system fragmentation** — not on row counts. Modern warehouses handle tens of millions of rows without breathing, and a verdict justified by volume will be dismissed by the first competent data engineer who reads it, taking your correct conclusions with it.\n\n### 6. Say what you would change\n\nThey asked. A diagnosis without a prescription is half a deliverable. The strongest form is a **disposition table** — their type, what happens to it, why — because it converts your review into their Monday morning.\n\nAdd two things:\n\n- **A first wave.** Name the handful of types that close one of their stated use cases end to end. Without this you hand back a bigger model than the one you criticised for being too big.\n- **The blocking decisions.** The questions that must be answered before anyone writes a pipeline, with names attached where possible.\n\n### 7. Credit what is right — this is not optional\n\nName three or four specific things the model gets right, with the reason. Not a disclaimer, not a compliment — specific correct decisions.\n\nThe reason is practical. Your reader has to take this back to the people who wrote it. Four paragraphs of earned credit are what make thirty criticisms survivable in that room, and a review that lands is worth more than a review that is merely right. Reviewers comparing outputs from this skill picked this out unprompted as the thing that decided which review they would rather receive.\n\n## Tone\n\nThey asked for blunt; give them blunt. Blunt means *specific and unhedged*, not harsh. \"This is a saved filter wearing an object costume\" is blunt. \"The team clearly didn't think this through\" is not blunt, it is rude, and it makes the review unusable in the room it has to be read in.\n\nOpen by separating the team from the work where you honestly can — most bad models are written by competent people under constraints, and the facts you need are usually already in their notes.\n\n## What to check in your own review before sending\n\n- **Every criticism is actually true of their model.** Criticising them for something they did do is the fastest way to lose the room. Check the model text for each finding before writing it.\n- **Counts in your prose match your tables.** \"Three of the five requirements\" above a table showing four is the kind of error that makes a reader re-check everything else.\n- **Your own proposed model does not repeat the defects you just criticised.** This is the most common self-inflicted wound in a review: holding them to a standard your counter-proposal does not meet.\n- **Nothing is asserted about their business that they did not tell you.** Where you assumed, say so at the top, and say which conclusions move if the assumption is wrong.\n\n## Output shape\n\n```\n1. Verdict — one paragraph. Build it, fix it, or don't build it\n2. What they got right — 3-4 specific things\n3. Their requirements, scored — can this model answer what they said it must?\n4. Findings — numbered, most consequential first, each: location · defect · minimal fix\n5. What I'd change — disposition table, their type by their type\n6. First wave — the subset that closes one use case\n7. Data readiness — the verdict and what it means for sequencing\n8. Blocking decisions — what must be answered before a pipeline is written\n9. Assumptions I made — and which conclusions move if they are wrong\n```\n\nFindings are the body; keep everything else tight. If a section would be empty, delete it.\n",
73
73
  "designing-frontera-blueprint/references/self-review.md": "# Self-review — before declaring a design done\n\nDesign finds very few of its own defects. A review pass on a substantial model routinely returns dozens, and a second pass reliably finds defects the first pass's *fixes* introduced.\n\n**Run two passes on the full path, one on the short path.** A second pass over a nine-type model is ceremony; a second pass over a fifty-type model is where half the real findings come from.\n\n## Order of operations\n\n1. **Run the linter.** `python3 scripts/lint_ontology.py model.yaml` catches the mechanical class — orphan types, missing join keys, undeclared metric properties, uncovered types, over-budget agents, unreachable status values, actions writing non-editable properties.\n2. **Then a human-or-agent pass** for what static checks cannot see.\n3. **Fix, re-lint, re-review.**\n\nRunning the linter first is not just efficiency — it keeps the expensive reviewer focused on judgement rather than bookkeeping.\n\n## The brief\n\nIf you can dispatch an independent reviewer, give them this verbatim. If you are reviewing your own work, read it as instructions to yourself and be correspondingly suspicious of anything you are pleased with:\n\n> Read the design document and `model.yaml` and act as an adversarial reviewer. Your job is to find **defects**, not to praise. Do not rewrite the document.\n>\n> Check four categories:\n>\n> **1. Internal consistency.** Does every part of the document agree with every other part and with the model file? Look specifically for: properties referenced in one section and absent from another; actions writing properties that do not exist or are not marked editable; metrics whose measures or dimensions reference undeclared properties; object sets filtering on properties or enum values that do not exist; counts stated in prose that disagree with the tables; cross-references pointing at the wrong section.\n>\n> **2. Principle violations.** The document claims to follow stated principles. Where does it not? Look for god objects, duplicated canonical concepts, hierarchies deeper than one level, types that are really states, patterns applied inconsistently, and anything that models a system rather than the business.\n>\n> **3. Platform-constraint violations.** The document states its platform assumptions. Which parts of the design would not compile or run under them? Trace the traversal path for each competency question and flag any that exceed the ceiling without a stated resolution.\n>\n> **4. Overclaiming.** Which factual statements are made with more confidence than their sources support? Which claims does the document elsewhere list as unverified? Which numbers do not reconcile against other numbers in the same document?\n>\n> Return a numbered list, most severe first. For each: **location, what is wrong, and the minimal fix.** Be terse and specific. Only real defects — no style notes. If a section is clean, do not mention it.\n\n## What the second pass catches\n\nWorth knowing so it is not a surprise:\n\n- **Fixes that introduced new inconsistencies** — a property added to one section and not the register.\n- **Specifications that most implementers do not satisfy** — an interface or a naming contract declared, then violated by the majority of the types that claim to conform. This is nearly impossible to see while writing and trivial to check mechanically.\n- **Grants that contradict a stated classification** — an agent given a type the document says is restricted elsewhere.\n- **Newly unreachable things** — a status value whose producing action was removed during the fixes.\n\n## The full checklist\n\n**Scope and derivation**\n\n- [ ] Competency questions written before entities were named, 5–10 per use case\n- [ ] Every object type appears in ≥ 1 question; types with zero are cut or justified\n- [ ] Wave 1 closes on one high-return use case, not the full map\n- [ ] Platform assumptions are an explicit, falsifiable list\n\n**Structure**\n\n- [ ] No type triggers the subset test, or the trade is named\n- [ ] No property triggers the ownership test, or the copy is registered\n- [ ] No type is really a state\n- [ ] No god object, no mirrored source schema, no hierarchy deeper than one\n- [ ] Patterns applied consistently, or the exception is explained\n- [ ] Entity and observation are separate types wherever a row is a measurement or event\n\n**Temporal**\n\n- [ ] Grain explicit per type; entity / event / snapshot chosen deliberately\n- [ ] Bitemporality decided explicitly for effective-dated reference data and restated periods\n- [ ] Every trend question has a snapshot type behind it\n\n**Operational**\n\n- [ ] Every action is a human or agent decision; deterministic work is a pipeline\n- [ ] Parameters unambiguous, categorical ones constrained enums\n- [ ] Idempotency, maker–checker and forward-correction engineered explicitly\n- [ ] Every status value reachable by an action or a named pipeline\n- [ ] High-blast-radius actions require a human submitter\n- [ ] Unenforceable submission criteria marked advisory and re-checked in the quality checks\n\n**Agent readiness**\n\n- [ ] No agent over the tool budget, counting types, actions and metrics\n- [ ] The executive agent gets metrics plus entry points, not everything\n- [ ] Both disambiguation registers exist\n- [ ] Every description states grain, source, cadence and what it is not to be confused with\n- [ ] No isolated types; everything within the traversal budget\n- [ ] Every agent has assigned competency questions **and is granted every type those questions traverse** — the linter checks this; do not promise an agent against a question it cannot reach\n- [ ] Every correcting action moves the current-state value, not only the historical record\n- [ ] One temporal mechanism per concept, not three overlapping ones\n- [ ] The delivery plan is consistent with the readiness verdict — a six-week schedule above a \"not ready\" verdict is a contradiction a reader will find\n\n**Proof**\n\n- [ ] Readiness tier stated honestly, with the mart contract specified\n- [ ] Linter clean of errors\n- [ ] Two review passes; every finding resolved or explicitly declined with a reason\n- [ ] Evaluation suite with numeric thresholds that can fail\n- [ ] Named steward per domain; breaking-change list defined; retirement mechanism live\n\n## Feed the mechanical findings back\n\nIf a review pass keeps finding the same class of defect by hand, that class belongs in the linter. Extending `scripts/lint_ontology.py` is usually a few lines and pays for itself on the next engagement — this is the highest-leverage improvement available to the workflow itself.\n",
74
- "designing-frontera-blueprint/references/solution-patterns.md": "# Solution patterns that held up in delivery\n\nShapes from solutions that went live, where Agents, workflows and Apps all\nwork over the same records. Each is a default to start from; depart from it\non purpose. Never take a name from this file into a real model: the names\nhere are illustrations.\n\n## Records an Agent creates\n\n- **Start on a blank dataset**, so only real records exist. Sample rows for\n testing come from the local runtime's generator, never from the customer's\n dataset.\n- **Separate the person from the need.** A contact can raise more than one\n need, so model them as two linked types (a person or account, one to many\n needs) from the start. Retrofitting the split later means a migration.\n- **Pair a governed create Action for the Agent with a separate edit Action\n for people.** The Agent files; people triage. The Agent does not need read\n access to what it creates in order to create it.\n- **Store the conversation id on created records**, so people can open the\n transcript the record came from.\n\n## Suggestions apart from decisions\n\n- Enrichment, scoring and classification by a workflow write **suggestion\n fields** through their own Action and capability (for example\n `request:enrich`). Status, priority and owner belong to people, through\n theirs (`request:classify`).\n- A description says who writes the type and through which Action: \"Set by\n people, through classifyRequest\".\n\n## Satellite types\n\nWhen a workflow produces a lot about one record (research, an assessment, a\nbrief), put it in **satellite types keyed by the parent's id**, linked one to\none, instead of widening the core type. The core dataset keeps its columns,\nand each satellite can change on its own.\n\n## Internal types\n\nTypes people inside the organization use, which customers never see, get\ndisplay names prefixed \"[Internal]\" and their own icon and colour, so they\nstand apart in a model shared by many teams and applications.\n\n## Names that travel\n\n- An organization shares one Blueprint, and a workspace sees only its slice.\n Generic names (Activity, Opportunity, Customer, Ticket) are often taken by\n another team outside the slice. Prefer specific names (SalesActivity).\n- Keep API names stable once anything references them. Rename display names\n freely.\n\n## Fields and datasets\n\n- A dataset's first load fixes its columns. Declare every non-key column\n nullable unless it truly cannot be empty.\n- Adding a column means a new dataset revision or a rebind; removing and\n adding properties belong in separate changes.\n- Every field an Action writes must be declared editable on the object type.\n Check that it reaches the target workspace after delivery, because some\n routes drop it.\n- Mark contact fields confidential, and grant sensitive read to the roles,\n workspaces and Agents whose job needs them in the same change. A reader\n without it sees empty values, which look like missing data.\n\n## Grants\n\nList every type that a workspace's Apps, workflows and Agents read in the\nworkspace grants file. A local runtime may grant everything automatically,\nhiding a missing grant until delivery.\n\n## Actions in delivery\n\nNew Actions ship disabled. Plan their deployment, review by a second person\nwhere the organization requires it, enabling and capability grants as part\nof delivery, with an owner for each step.\n",
74
+ "designing-frontera-blueprint/references/solution-patterns.md": "# Solution patterns that held up in delivery\n\nShapes from solutions that went live, where Agents, workflows and Apps all\nwork over the same records. Each is a default to start from; depart from it\non purpose. Never take a name from this file into a real model: the names\nhere are illustrations.\n\n## Records an Agent creates\n\n- **Start on a blank dataset**, so only real records exist. Sample rows for\n testing come from the local runtime's generator, never from the customer's\n dataset.\n- **Separate the person from the need.** A contact can raise more than one\n need, so model them as two linked types (a person or account, one to many\n needs) from the start. Retrofitting the split later means a migration.\n- **Pair a governed create Action for the Agent with a separate edit Action\n for people.** The Agent files; people triage. The Agent does not need read\n access to what it creates in order to create it.\n- **Store the conversation id on created records**, so people can open the\n transcript the record came from.\n\n## Suggestions apart from decisions\n\n- Enrichment, scoring and classification by a workflow write **suggestion\n fields** through their own Action and capability (for example\n `request:enrich`). Status, priority and owner belong to people, through\n theirs (`request:classify`).\n- A description says who writes the type and through which Action: \"Set by\n people, through classifyRequest\".\n\n## Satellite types\n\nWhen a workflow produces a lot about one record (research, an assessment, a\nbrief), put it in **satellite types keyed by the parent's id**, linked one to\none, instead of widening the core type. The core dataset keeps its columns,\nand each satellite can change on its own.\n\n## Internal types\n\nTypes people inside the organization use, which customers never see, get\ndisplay names prefixed \"[Internal]\" and their own icon and colour, so they\nstand apart in a model shared by many teams and applications.\n\n## Names that travel\n\n- An organization shares one Blueprint, and a workspace sees only its slice.\n Generic names (Activity, Opportunity, Customer, Ticket) are often taken by\n another team outside the slice. Prefer specific names (SalesActivity).\n- Keep API names stable once anything references them. Rename display names\n freely.\n\n## Fields and datasets\n\n- A dataset's first load fixes its columns. Declare every non-key column\n nullable unless it truly cannot be empty.\n- Adding a column means a new dataset revision or a rebind; removing and\n adding properties belong in separate changes.\n- Every field an Action writes must be declared editable on the object type.\n Check that it reaches the target workspace after delivery, because some\n routes drop it.\n- Mark contact fields confidential, and grant sensitive read to the roles,\n workspaces and Agents whose job needs them in the same change. A reader\n without it sees empty values, which look like missing data.\n\n## Grants\n\nList every type that a workspace's Apps, workflows and Agents read in the\nworkspace grants file. A local runtime may grant everything automatically,\nhiding a missing grant until delivery.\n\n## Actions in delivery\n\nNew Actions ship disabled. Plan their deployment, review, enabling and\ncapability grants as part of delivery, with an owner for each step. You\nmay review your own Action when it only edits Blueprint records; an Action\nthat calls another system needs a second person.\n",
75
75
  "designing-frontera-blueprint/scripts/lint_ontology.py": "#!/usr/bin/env python3\n\"\"\"\nlint_ontology.py — static checks over a model.yaml ontology definition.\n\nThese are the defects an adversarial review pass finds by hand: orphan object\ntypes, links whose join key does not exist, metrics referencing undeclared\nproperties, object types no competency question asks for, agents over tool\nbudget, status values no action produces. Catching them here is faster, cheaper\nand repeatable, which frees a human reviewer for the things static checks\ngenuinely cannot see — whether the model reflects the business.\n\nUsage:\n python3 lint_ontology.py model.yaml\n python3 lint_ontology.py model.yaml --json\n python3 lint_ontology.py model.yaml --severity error # errors only\n\nExit codes: 0 clean or warnings only · 1 errors found · 2 could not read input.\n\nSchema: see assets/model-schema.md. Every section is optional; the linter\nchecks what is present and stays quiet about what is not, so it is useful from\nthe first ten minutes of a design rather than only at the end.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport re\nimport sys\nfrom collections import Counter, defaultdict, deque\n\ntry:\n import yaml\nexcept ImportError:\n sys.stderr.write(\n \"PyYAML is required: pip install pyyaml --break-system-packages\\n\"\n )\n sys.exit(2)\n\n\nclass Findings:\n def __init__(self) -> None:\n self.items: list[dict] = []\n\n def add(self, severity: str, check: str, where: str, message: str,\n fix: str = \"\") -> None:\n self.items.append({\n \"severity\": severity, \"check\": check, \"where\": where,\n \"message\": message, \"fix\": fix,\n })\n\n def error(self, *a, **k) -> None:\n self.add(\"error\", *a, **k)\n\n def warn(self, *a, **k) -> None:\n self.add(\"warning\", *a, **k)\n\n def info(self, *a, **k) -> None:\n self.add(\"info\", *a, **k)\n\n\n# --------------------------------------------------------------------------\n# helpers\n# --------------------------------------------------------------------------\n\ndef props_of(ot: dict) -> dict[str, dict]:\n return {p[\"api_name\"]: p for p in ot.get(\"properties\") or []\n if isinstance(p, dict) and p.get(\"api_name\")}\n\n\ndef normalise(name: str) -> str:\n \"\"\"Lowercase alphanumeric core of a name, for near-duplicate detection.\"\"\"\n return re.sub(r\"[^a-z0-9]\", \"\", name.lower())\n\n\ndef singularish(name: str) -> str:\n n = normalise(name)\n for suf in (\"ies\", \"es\", \"s\"):\n if n.endswith(suf) and len(n) > len(suf) + 2:\n return n[: -len(suf)]\n return n\n\n\n# --------------------------------------------------------------------------\n# checks\n# --------------------------------------------------------------------------\n\ndef check_object_types(m: dict, f: Findings) -> None:\n types = m.get(\"object_types\") or []\n if not types:\n f.warn(\"no-object-types\", \"model\", \"No object types defined yet.\")\n return\n\n seen: dict[str, str] = {}\n for ot in types:\n name = ot.get(\"api_name\")\n if not name:\n f.error(\"missing-api-name\", \"object_types\",\n \"An object type has no api_name.\")\n continue\n where = f\"objectType:{name}\"\n\n if name in seen:\n f.error(\"duplicate-type\", where, f\"'{name}' is defined twice.\")\n seen[name] = name\n\n if not re.match(r\"^[A-Z][A-Za-z0-9]*$\", name):\n f.warn(\"naming\", where,\n f\"'{name}' is not PascalCase alphanumeric.\",\n \"Object type names are tool names to an agent; keep them \"\n \"conventional and self-describing.\")\n\n p = props_of(ot)\n pk = ot.get(\"primary_key\")\n if not pk:\n f.error(\"no-primary-key\", where, \"No primary_key declared.\")\n elif pk not in p:\n f.error(\"pk-not-declared\", where,\n f\"primary_key '{pk}' is not among this type's properties.\")\n\n tp = ot.get(\"title_property\")\n if tp and tp not in p:\n f.error(\"title-not-declared\", where,\n f\"title_property '{tp}' is not among this type's properties.\")\n\n desc = (ot.get(\"description\") or \"\").strip()\n if not desc:\n f.error(\"no-description\", where,\n \"No description. An agent selects this type by its \"\n \"description; without one it is invisible or misused.\")\n elif len(desc) < 60:\n f.warn(\"thin-description\", where,\n \"Description is very short.\",\n \"State what it is, its grain, its source and refresh, and \"\n \"what it is not to be confused with.\")\n elif \"not to be confused\" not in desc.lower() and len(types) > 8:\n f.info(\"no-disambiguation\", where,\n \"Description does not disambiguate against a similar type.\",\n \"In a model this size, near-synonyms are the main cause of \"\n \"an agent picking the wrong type.\")\n\n if ot.get(\"kind\") == \"extension\" and not ot.get(\"extends\"):\n f.error(\"extension-no-core\", where,\n \"kind is 'extension' but no 'extends' is set.\")\n\n if ot.get(\"status\") not in (None, \"active\", \"experimental\", \"deprecated\"):\n f.warn(\"bad-status\", where,\n f\"status '{ot.get('status')}' is not one of \"\n \"active/experimental/deprecated.\")\n\n # near-duplicate names: the synonym trap\n buckets: dict[str, list[str]] = defaultdict(list)\n for name in seen:\n buckets[singularish(name)].append(name)\n for _, names in buckets.items():\n if len(names) > 1:\n f.warn(\"near-duplicate-names\", \"object_types\",\n f\"Names are near-identical: {', '.join(sorted(names))}.\",\n \"An agent cannot reliably choose between them. Rename, or \"\n \"add an explicit contrast to each description.\")\n\n\nABSTRACT_NAMES = {\n \"party\", \"partyrole\", \"entity\", \"resource\", \"item\", \"thing\", \"object\",\n \"element\", \"node\", \"instance\", \"master\", \"reference\", \"data\", \"info\",\n \"detail\", \"header\", \"record\", \"attribute\", \"artifact\", \"artefact\",\n}\nABSTRACT_SUFFIXES = (\"Entity\", \"Master\", \"Info\", \"Data\", \"Detail\", \"Header\",\n \"Record\", \"Object\", \"Item\")\n\n\ndef check_business_language(m: dict, f: Findings) -> None:\n \"\"\"Object type names should be words the business says out loud.\n\n An abstraction imported from a reference model — Party, Entity, Resource —\n is a name nobody in the company uses and nothing an agent can match to a\n user's question. This check cannot know the business's vocabulary, so it\n flags the names that are abstract in every business and asks.\n \"\"\"\n for ot in m.get(\"object_types\") or []:\n name = ot.get(\"api_name\")\n if not name:\n continue\n where = f\"objectType:{name}\"\n low = name.lower()\n\n if low in ABSTRACT_NAMES:\n f.warn(\"abstract-type-name\", where,\n f\"'{name}' is an abstraction, not a word a business uses.\",\n \"Say it out loud: would anyone in this company ask to \"\n \"'pull up the \" + low + \"'? If the real word is customer, \"\n \"carrier or supplier, use that. Keep this name only if the \"\n \"business genuinely says it — legal and insurance do say \"\n \"'party'.\")\n elif any(name.endswith(s) for s in ABSTRACT_SUFFIXES) and len(name) > 6:\n f.info(\"generic-suffix\", where,\n f\"'{name}' ends in a generic suffix.\",\n \"Often a sign the concrete noun was avoided. If the \"\n \"business calls it something shorter and more specific, \"\n \"use theirs.\")\n\n\ndef check_links(m: dict, f: Findings) -> None:\n types = {ot[\"api_name\"]: ot for ot in (m.get(\"object_types\") or [])\n if ot.get(\"api_name\")}\n links = m.get(\"link_types\") or []\n seen = set()\n\n for lk in links:\n name = lk.get(\"api_name\", \"<unnamed>\")\n where = f\"link:{name}\"\n if name in seen:\n f.error(\"duplicate-link\", where, f\"'{name}' is defined twice.\")\n seen.add(name)\n\n src, dst = lk.get(\"from\"), lk.get(\"to\")\n for side, t in ((\"from\", src), (\"to\", dst)):\n if t not in types:\n f.error(\"link-endpoint-missing\", where,\n f\"{side} type '{t}' is not defined.\")\n if src not in types or dst not in types:\n continue\n\n card = lk.get(\"cardinality\")\n if card not in (\"one_to_one\", \"one_to_many\", \"many_to_one\",\n \"many_to_many\"):\n f.error(\"bad-cardinality\", where,\n f\"cardinality '{card}' is not a recognised value.\")\n\n if card == \"many_to_many\":\n if not lk.get(\"join_model\"):\n f.error(\"mn-no-join-model\", where,\n \"many_to_many link has no join_model.\",\n \"A many-to-many link needs a bridge table; without one \"\n \"it cannot be built.\")\n continue\n\n fp, tp = lk.get(\"from_property\"), lk.get(\"to_property\")\n sp, dp = props_of(types[src]), props_of(types[dst])\n if not fp or fp not in sp:\n f.error(\"join-key-missing\", where,\n f\"from_property '{fp}' is not a property of {src}.\",\n \"This link cannot be built. It is the single most common \"\n \"defect in a hand-written model.\")\n if not tp or tp not in dp:\n f.error(\"join-key-missing\", where,\n f\"to_property '{tp}' is not a property of {dst}.\")\n\n # the \"one\" side must be a key\n one_side, one_prop = (src, fp) if card in (\"one_to_one\", \"one_to_many\") \\\n else (dst, tp)\n ot = types[one_side]\n if one_prop and one_prop != ot.get(\"primary_key\") \\\n and not (props_of(ot).get(one_prop) or {}).get(\"unique\"):\n f.warn(\"one-side-not-unique\", where,\n f\"'{one_prop}' on {one_side} is neither the primary key nor \"\n \"marked unique.\",\n \"Cardinality is a promise the data has to keep. Mark the \"\n \"property unique or correct the cardinality.\")\n\n if not lk.get(\"from_phrase\") or not lk.get(\"to_phrase\"):\n f.info(\"no-phrases\", where,\n \"No directional phrases.\",\n \"Cardinality says a link exists; a phrase says what \"\n \"traversing it means. Agents use the phrase.\")\n\n\ndef check_isolated(m: dict, f: Findings) -> None:\n types = [ot[\"api_name\"] for ot in (m.get(\"object_types\") or [])\n if ot.get(\"api_name\")]\n linked: set[str] = set()\n for lk in m.get(\"link_types\") or []:\n linked.add(lk.get(\"from\"))\n linked.add(lk.get(\"to\"))\n for t in types:\n if t not in linked:\n f.error(\"isolated-type\", f\"objectType:{t}\",\n f\"'{t}' appears in no link.\",\n \"A type reachable only by filtering is a table, not part \"\n \"of an ontology — an agent will not find it by traversal.\")\n\n\ndef check_reachability(m: dict, f: Findings) -> None:\n \"\"\"Every type should sit within traversal_limit hops of some agent entry point.\"\"\"\n limit = int((m.get(\"meta\") or {}).get(\"traversal_limit\") or 3)\n types = {ot[\"api_name\"] for ot in (m.get(\"object_types\") or [])\n if ot.get(\"api_name\")}\n if not types:\n return\n adj: dict[str, set[str]] = defaultdict(set)\n for lk in m.get(\"link_types\") or []:\n a, b = lk.get(\"from\"), lk.get(\"to\")\n if a in types and b in types:\n adj[a].add(b)\n adj[b].add(a)\n\n entries: set[str] = set()\n for ag in m.get(\"agents\") or []:\n entries.update(t for t in (ag.get(\"object_types\") or []) if t in types)\n if not entries:\n return\n\n reach: set[str] = set()\n for start in entries:\n seen = {start: 0}\n q = deque([start])\n while q:\n cur = q.popleft()\n if seen[cur] >= limit:\n continue\n for nxt in adj[cur]:\n if nxt not in seen:\n seen[nxt] = seen[cur] + 1\n q.append(nxt)\n reach |= set(seen)\n\n for t in sorted(types - reach):\n f.warn(\"unreachable-type\", f\"objectType:{t}\",\n f\"'{t}' is more than {limit} hops from every agent's entry \"\n \"points.\",\n \"Either grant it to an agent directly, or add a shortcut link \"\n \"and register it. Types beyond the ceiling are invisible.\")\n\n\ndef check_competency(m: dict, f: Findings) -> None:\n qs = m.get(\"competency_questions\") or []\n types = [ot[\"api_name\"] for ot in (m.get(\"object_types\") or [])\n if ot.get(\"api_name\")]\n if not qs:\n f.error(\"no-competency-questions\", \"model\",\n \"No competency questions defined.\",\n \"Without them there is no way to tell a derived object type \"\n \"from an enumerated one. Write them before modelling.\")\n return\n\n covered: set[str] = set()\n for q in qs:\n qid = q.get(\"id\", \"<no id>\")\n for t in q.get(\"types\") or []:\n if t not in types:\n f.error(\"question-unknown-type\", f\"question:{qid}\",\n f\"references undefined object type '{t}'.\")\n covered.add(t)\n text = (q.get(\"text\") or \"\").strip()\n if text and not text.endswith(\"?\"):\n f.info(\"question-not-a-question\", f\"question:{qid}\",\n \"Does not read as a question.\",\n \"If it cannot be answered with a specific number or list, \"\n \"it is a theme — and themes do not justify object types.\")\n\n counts = Counter(t for q in qs for t in (q.get(\"types\") or []))\n for t in types:\n if t not in covered:\n f.error(\"uncovered-type\", f\"objectType:{t}\",\n f\"'{t}' appears in no competency question.\",\n \"It was enumerated, not derived. Cut it, or write the \"\n \"question that justifies it.\")\n elif counts[t] == 1:\n f.info(\"single-question-type\", f\"objectType:{t}\",\n f\"'{t}' is justified by exactly one question — the \"\n \"weakest-justified tier, and the first candidate for \"\n \"retirement if that question goes unasked.\")\n\n\ndef check_metrics(m: dict, f: Findings) -> None:\n types = {ot[\"api_name\"]: ot for ot in (m.get(\"object_types\") or [])\n if ot.get(\"api_name\")}\n for mt in m.get(\"metrics\") or []:\n name = mt.get(\"api_name\", \"<unnamed>\")\n where = f\"metric:{name}\"\n home = mt.get(\"object_type\")\n if home not in types:\n f.error(\"metric-home-missing\", where,\n f\"home object type '{home}' is not defined.\")\n continue\n p = props_of(types[home])\n\n measures = mt.get(\"measures\") or []\n if not measures:\n f.error(\"metric-no-measures\", where, \"No measures defined.\")\n for meas in measures:\n prop = meas.get(\"property\")\n if prop and prop not in p:\n f.error(\"measure-undeclared\", where,\n f\"measure references '{prop}', which is not a property \"\n f\"of {home}.\")\n elif prop and p[prop].get(\"type\") != \"measure\":\n f.warn(\"measure-not-numeric\", where,\n f\"'{prop}' is typed '{p[prop].get('type')}', not \"\n \"'measure'.\")\n\n for dim in mt.get(\"dimensions\") or []:\n if dim not in p:\n f.error(\"dimension-undeclared\", where,\n f\"dimension '{dim}' is not a property of {home}.\",\n \"A metric cannot traverse a link unless the platform \"\n \"supports it — declare the dimension or derive it onto \"\n \"this type.\")\n\n ts = mt.get(\"timeseries\")\n if ts:\n if ts not in p:\n f.error(\"timeseries-undeclared\", where,\n f\"timeseries '{ts}' is not a property of {home}.\")\n elif p[ts].get(\"type\") != \"time\":\n f.warn(\"timeseries-not-time\", where,\n f\"timeseries '{ts}' is not typed 'time'.\")\n elif types[home].get(\"kind\") == \"snapshot\":\n f.warn(\"snapshot-no-timeseries\", where,\n \"Metric over a snapshot type has no timeseries property — \"\n \"the reason the snapshot exists is trend.\")\n\n if types[home].get(\"kind\") == \"entity\" and ts is None:\n f.info(\"trend-on-current-state\", where,\n f\"'{home}' is a current-state type, so this metric has no \"\n \"history to walk.\",\n \"If anyone will ask how the number moved, that needs a \"\n \"snapshot type keyed on (entityId, period).\")\n\n\ndef check_actions(m: dict, f: Findings) -> None:\n types = {ot[\"api_name\"]: ot for ot in (m.get(\"object_types\") or [])\n if ot.get(\"api_name\")}\n produced: set[str] = set(\n (m.get(\"meta\") or {}).get(\"pipeline_produced_values\") or [])\n\n for ac in m.get(\"actions\") or []:\n name = ac.get(\"api_name\", \"<unnamed>\")\n where = f\"action:{name}\"\n target = ac.get(\"target\")\n if target and target not in types:\n f.error(\"action-target-missing\", where,\n f\"target '{target}' is not defined.\")\n\n radius = ac.get(\"blast_radius\")\n if radius not in (\"low\", \"medium\", \"high\", None):\n f.warn(\"bad-blast-radius\", where,\n f\"blast_radius '{radius}' is not low/medium/high.\")\n hitl = ac.get(\"human_in_the_loop\")\n if radius == \"high\" and hitl not in (\"required\",):\n f.error(\"high-radius-automatic\", where,\n \"A high-blast-radius action is not marked \"\n \"human_in_the_loop: required.\",\n \"Anything that moves money or commits to a counterparty \"\n \"should be prepared by an agent and submitted by a named \"\n \"human.\")\n\n for w in ac.get(\"writes\") or []:\n ot_name = w.get(\"object_type\") or target\n prop = w.get(\"property\")\n if ot_name not in types:\n f.error(\"write-target-missing\", where,\n f\"writes to undefined type '{ot_name}'.\")\n continue\n p = props_of(types[ot_name])\n if prop not in p:\n f.error(\"write-undeclared\", where,\n f\"writes '{prop}', which is not a property of \"\n f\"{ot_name}.\")\n elif not p[prop].get(\"editable\"):\n f.error(\"write-not-editable\", where,\n f\"writes '{ot_name}.{prop}', which is not marked \"\n \"editable.\",\n \"Mark it editable, or the action cannot run.\")\n elif p[prop].get(\"derived\"):\n f.error(\"write-derived\", where,\n f\"writes '{ot_name}.{prop}', which is derived.\",\n \"A derived value has one writer — its definition. \"\n \"Add a separate override property the action owns.\")\n if w.get(\"sets\"):\n produced.add(f\"{ot_name}.{prop}={w['sets']}\")\n\n for sc in ac.get(\"submission_criteria\") or []:\n if sc.get(\"enforced\") is False:\n f.info(\"advisory-criterion\", where,\n f\"criterion is advisory: {sc.get('text', '')}\",\n \"Re-check it post-hoc in the quality checks, and do not \"\n \"let an agent believe it is guaranteed.\")\n\n # unreachable enum values\n for ot in m.get(\"object_types\") or []:\n for prop in ot.get(\"properties\") or []:\n vals = prop.get(\"enum\")\n if not vals:\n continue\n for v in vals:\n key = f\"{ot['api_name']}.{prop['api_name']}={v}\"\n if key not in produced:\n f.warn(\"unreachable-status\", f\"objectType:{ot['api_name']}\",\n f\"'{prop['api_name']}' can be '{v}', but no action \"\n \"produces it.\",\n \"Either add the action, or record the pipeline in \"\n \"meta.pipeline_produced_values. A saved query \"\n \"filtering on an unreachable value returns nothing, \"\n \"forever, silently.\")\n\n\ndef check_agents(m: dict, f: Findings) -> None:\n budget = int((m.get(\"meta\") or {}).get(\"agent_tool_budget\") or 20)\n types = {ot[\"api_name\"] for ot in (m.get(\"object_types\") or [])\n if ot.get(\"api_name\")}\n actions = {a[\"api_name\"] for a in (m.get(\"actions\") or [])\n if a.get(\"api_name\")}\n metrics = {mt[\"api_name\"] for mt in (m.get(\"metrics\") or [])\n if mt.get(\"api_name\")}\n qids = {q.get(\"id\") for q in (m.get(\"competency_questions\") or [])}\n\n for ag in m.get(\"agents\") or []:\n name = ag.get(\"name\", \"<unnamed>\")\n where = f\"agent:{name}\"\n ots = ag.get(\"object_types\") or []\n acs = ag.get(\"actions\") or []\n mts = ag.get(\"metrics\") or []\n\n for t in ots:\n if t not in types:\n f.error(\"agent-unknown-type\", where,\n f\"granted undefined object type '{t}'.\")\n for a in acs:\n if a not in actions:\n f.error(\"agent-unknown-action\", where,\n f\"granted undefined action '{a}'.\")\n for mt in mts:\n if mt not in metrics:\n f.error(\"agent-unknown-metric\", where,\n f\"granted undefined metric '{mt}'.\")\n for q in ag.get(\"answers\") or []:\n if q not in qids:\n f.error(\"agent-unknown-question\", where,\n f\"claims to answer unknown question '{q}'.\")\n\n total = len(ots) + len(acs) + len(mts)\n if total > budget:\n f.error(\"agent-over-budget\", where,\n f\"{total} tools ({len(ots)} types, {len(acs)} actions, \"\n f\"{len(mts)} metrics) exceeds the budget of {budget}.\",\n \"Tool selection becomes the dominant failure mode above \"\n \"roughly twenty. Split the agent or hand off.\")\n elif total > budget * 0.8:\n f.warn(\"agent-near-budget\", where,\n f\"{total} tools is close to the budget of {budget}.\")\n\n if not ag.get(\"answers\"):\n f.info(\"agent-no-questions\", where,\n \"No competency questions assigned.\",\n \"An agent with no questions has no evaluation suite.\")\n\n # Does the agent hold the types its own questions traverse?\n qmap = {q.get(\"id\"): (q.get(\"types\") or [])\n for q in (m.get(\"competency_questions\") or [])}\n granted = set(ots)\n for q in ag.get(\"answers\") or []:\n missing = [t for t in qmap.get(q, []) if t not in granted]\n if missing:\n f.error(\"agent-cannot-answer\", where,\n f\"assigned {q} but is not granted: \"\n f\"{', '.join(sorted(missing))}.\",\n \"The agent is promised against a question it cannot \"\n \"reach. Grant the types, reassign the question, or \"\n \"hand off — but do not ship the promise.\")\n\n\ndef check_actions_write_something(m: dict, f: Findings) -> None:\n \"\"\"Editable properties with no action behind them are an open write path.\"\"\"\n written = {(w.get(\"object_type\"), w.get(\"property\"))\n for ac in (m.get(\"actions\") or [])\n for w in (ac.get(\"writes\") or [])}\n for ot in m.get(\"object_types\") or []:\n for p in ot.get(\"properties\") or []:\n if p.get(\"editable\") and (ot[\"api_name\"], p[\"api_name\"]) not in written:\n f.warn(\"editable-no-action\", f\"objectType:{ot['api_name']}\",\n f\"'{p['api_name']}' is editable but no action writes it.\",\n \"Either an action is missing, or the property should not \"\n \"be editable. An ungoverned write path is the thing \"\n \"actions exist to prevent.\")\n\n\ndef check_dead_properties(m: dict, f: Findings) -> None:\n \"\"\"A property nothing writes and nothing derives is a column of zeroes.\n\n This is the defect that survives every other check: the design says a\n roll-up \"follows automatically\", the property is declared, and no action,\n derivation or stated source ever puts a value in it. It ships reading 0.\n \"\"\"\n written = {(w.get(\"object_type\"), w.get(\"property\"))\n for ac in (m.get(\"actions\") or [])\n for w in (ac.get(\"writes\") or [])}\n for ot in m.get(\"object_types\") or []:\n name = ot.get(\"api_name\")\n if ot.get(\"kind\") == \"snapshot\":\n continue # snapshot measures come from the snapshot job by definition\n for prop in ot.get(\"properties\") or []:\n pn = prop.get(\"api_name\")\n if not pn or prop.get(\"type\") != \"measure\":\n continue\n if prop.get(\"derived\") or prop.get(\"source\"):\n continue\n if (name, pn) in written:\n continue\n f.warn(\"possibly-dead-measure\", f\"objectType:{name}\",\n f\"'{pn}' is a measure that no action writes, that is not \"\n \"marked derived, and that declares no source.\",\n \"Set `derived: true`, name a `source` (dataset | pipeline | \"\n \"action), or delete it. A roll-up that 'follows \"\n \"automatically' does not — it follows from a job somebody \"\n \"has to write, and until then it reads zero.\")\n\n\nCHECKS = [\n check_object_types, check_business_language, check_links, check_isolated, check_reachability,\n check_competency, check_metrics, check_actions, check_agents,\n check_actions_write_something, check_dead_properties,\n]\n\n\ndef lint(model: dict) -> Findings:\n f = Findings()\n for fn in CHECKS:\n try:\n fn(model, f)\n except Exception as exc: # a malformed section should not stop the rest\n f.error(\"linter-error\", fn.__name__,\n f\"check raised {type(exc).__name__}: {exc}\")\n return f\n\n\ndef render(f: Findings, min_sev: str) -> str:\n order = {\"error\": 0, \"warning\": 1, \"info\": 2}\n keep = [i for i in f.items if order[i[\"severity\"]] <= order[min_sev]]\n counts = Counter(i[\"severity\"] for i in f.items)\n\n if not keep:\n return (f\"clean — no findings at or above '{min_sev}'.\\n\"\n f\"({counts['error']} errors, {counts['warning']} warnings, \"\n f\"{counts['info']} info in total)\")\n\n lines: list[str] = []\n by_sev: dict[str, list[dict]] = defaultdict(list)\n for i in keep:\n by_sev[i[\"severity\"]].append(i)\n for sev in (\"error\", \"warning\", \"info\"):\n if not by_sev[sev]:\n continue\n lines.append(f\"\\n{sev.upper()}S ({len(by_sev[sev])})\")\n lines.append(\"-\" * 60)\n for i in by_sev[sev]:\n lines.append(f\" [{i['check']}] {i['where']}\")\n lines.append(f\" {i['message']}\")\n if i[\"fix\"]:\n lines.append(f\" → {i['fix']}\")\n lines.append(\"\")\n lines.append(f\"{counts['error']} errors, {counts['warning']} warnings, \"\n f\"{counts['info']} info\")\n return \"\\n\".join(lines)\n\n\ndef main() -> int:\n ap = argparse.ArgumentParser(description=__doc__,\n formatter_class=argparse.RawDescriptionHelpFormatter)\n ap.add_argument(\"model\", help=\"path to model.yaml\")\n ap.add_argument(\"--json\", action=\"store_true\", help=\"emit JSON\")\n ap.add_argument(\"--severity\", choices=[\"error\", \"warning\", \"info\"],\n default=\"info\", help=\"minimum severity to report\")\n args = ap.parse_args()\n\n try:\n with open(args.model) as fh:\n model = yaml.safe_load(fh) or {}\n except FileNotFoundError:\n sys.stderr.write(f\"no such file: {args.model}\\n\")\n return 2\n except yaml.YAMLError as exc:\n sys.stderr.write(f\"could not parse YAML: {exc}\\n\")\n return 2\n\n if not isinstance(model, dict):\n sys.stderr.write(\"model.yaml must be a mapping at the top level\\n\")\n return 2\n\n findings = lint(model)\n if args.json:\n print(json.dumps({\"findings\": findings.items,\n \"summary\": dict(Counter(i[\"severity\"]\n for i in findings.items))},\n indent=2))\n else:\n print(render(findings, args.severity))\n\n return 1 if any(i[\"severity\"] == \"error\" for i in findings.items) else 0\n\n\nif __name__ == \"__main__\":\n sys.exit(main())\n",
76
76
  "designing-frontera-blueprint/SKILL.md": "---\nname: designing-frontera-blueprint\ndescription: \"Use when designing or reviewing the shape of a Frontera Blueprint — which object types, links, metrics, Actions and agent grants an organization needs, and whether a proposed model is any good. Triggers on outcome language: \\\"model our business so agents can use it\\\", \\\"what object types do we need\\\", \\\"here is our proposed model, tell me what is wrong with it\\\", \\\"our agents give wrong answers over our data\\\". This is design method; authoring-frontera-blueprint applies the result. Not for database tuning, ORM mapping or plain ETL design.\"\n---\n\n# Designing a Frontera Blueprint\n\n> **This copy is Frontera-shaped and ships in the Frontera plugin.** A generic\n> copy lives in this repository at `.agents/skills/blueprint-ontology-design/`\n> for internal work on other platforms. The two are expected to diverge — they have different jobs — and\n> neither is a stale copy of the other.\n\nA model that only describes is a schema. This produces one a governed agent can *act* through — and one that survives review.\n\n## Start here: which job is this?\n\nThe two jobs share principles and share nothing else. Pick one, then work from its file. **Do not run both.**\n\n| Signal in the request | Mode | Work from |\n|---|---|---|\n| No existing model supplied. \"Design\", \"model our business\", \"what object types do we need\" | **Design** | `references/design-mode.md` |\n| An existing model, schema, entity list or ERD is supplied. \"Review\", \"audit\", \"what's wrong with\", \"before we build this\" | **Review** | `references/review-mode.md` |\n| Both — a model exists and they want it redesigned | Review first, then design. The review's findings become the design's constraints | both, in that order |\n| Genuinely unclear | Ask one question: *\"Do you want me to review what you have, or design from scratch?\"* | — |\n\n## Then: how much of it?\n\nEffort should follow the request, and the signals are in the request itself — not in a judgement about how big the domain feels.\n\n**Run the short path** when *any* of these is true. Deliver the model, the reasoning, and the honest verdicts; skip the ceremony.\n\n- They named a deadline or a size constraint — \"six weeks\", \"keep it tight\", \"not a cathedral\", \"just the X side\".\n- The scope is one use case or one process, not a business.\n- They asked a question rather than commissioning a document.\n\n**Short path means:** competency questions, the model, actions, agent scope, the readiness verdict, the wave plan, one self-review pass — and **something that runs**. A schema plus a worked example that executes beats another section of prose, and on a deadline it is what the client will judge you on. **Drop:** the interfaces section, stewardship and retirement policy, the separate platform-gaps document, the second review pass, standalone registers below roughly fifteen object types, and any section whose content would be \"not applicable here\".\n\n**Run the full path** when the scope is a whole business or several lines, no deadline was named, and the deliverable is explicitly a design document.\n\nTwo rules that hold on both paths, because they are where over-serving does real damage:\n\n- **Never write a section whose content is that it does not apply.** Silence is the correct way to note something was considered and excluded.\n- **Anything the requester named out loud is in the first wave.** If they said claims get \"assigned, reserved, revised, settled and restated\", all five are wave 1 — deferring one to a later wave while including something they never mentioned is a failure to listen, whatever the sequencing logic says.\n\n## The non-negotiables\n\nWhichever mode and whichever path, these are the things that cannot be fixed later, so they are worth defending under deadline pressure:\n\n- **Naming and semantic clarity.** Every object type is a real-world thing the business already has a word for. **Apply the say-it-out-loud test: if nobody in the company would use this word in a meeting, it is not an object type.** \"Pull up the customer\" is a sentence; \"pull up the party role\" is not.\n\n **Two rules that follow from this, and are violated constantly:**\n\n **Never take a name from this skill, its references, or its templates.** Every\n example here is illustration, not inventory. The names in `assets/model-schema.md`\n belong to an invented port-operations example and are wrong for your customer by\n construction. If you find yourself writing an object type whose name you first\n read in one of these files, you have skipped the only step that mattered.\n\n **Use their word, in their language.** If the business says *Nasabah*, the\n object type is `Nasabah`. Do not translate it to `Customer` for tidiness, and\n do not generalise it to something that fits any business. The model is read by\n the people who work there and by an agent matching their questions to type\n names; both lose when the word stops being theirs. A wrong name propagates into every application, every saved query and every agent prompt. This is also where the highest-value artefact lives — see the disambiguation registers in `references/actions-and-agents.md`. Independent reviewers of this skill's output called those registers the single best thing in the deliverable, twice.\n- **Identity.** A non-deterministic key destroys edits, links and citations. Never key on an ETL surrogate.\n- **Security design.** A grant that was too wide is not repairable after the fact.\n\nEverything else — normalisation, completeness, elegance — can be conceded and repaired. **Say which you are conceding, explicitly**, where you concede it.\n\n## Shared principles\n\n| Principle | What it prevents |\n|---|---|\n| **Real-world entities, in the business's own words** | Abstractions imported from a reference model — `Party`, `Entity`, `Resource` — that nobody in the business recognises and no agent can match to a question |\n| Model the domain, not the systems | Object types mirroring source-schema quirks, exposing technical columns |\n| Don't repeat yourself | Several source systems producing several answers to one question |\n| Open for extension, closed for modification | A core type where most properties are null for most rows |\n| Composition over deep hierarchies | Inheritance chains where a root change ripples through every query |\n\n**When something becomes an object type:** the business has a name for it, and it has independent identity and lifecycle, several properties, and relationships of its own. Take the *distinctions* a reference model teaches; never take its *names*. When it is an attribute of something else and not worth querying independently, it is a property. When it is a real-world relationship — not a join key inherited from a source system — it is a link.\n\n## The model file and the linter\n\nBoth modes produce **`model.yaml`** alongside the prose — schema in `assets/model-schema.md`. This is what makes the work checkable rather than merely plausible.\n\n```bash\npython3 scripts/lint_ontology.py model.yaml # human-readable\npython3 scripts/lint_ontology.py model.yaml --json # for CI\n```\n\nIt catches what a reviewer would otherwise find by hand: orphan types, links whose join key does not exist, metrics referencing undeclared properties, object types no competency question asks for, agents over tool budget, unreachable status values, actions writing non-editable properties. Run it after every change, not once at the end.\n\n**If you cite the linter to a client, ship it with the deliverable or do not cite it as authority** — a verification they cannot re-run is an assertion wearing a lab coat.\n\n**When reporting linter results to a human, report distinct defect classes, not instance counts.** \"Thirteen types have no description\" is one finding, not thirteen; presenting it as sixteen errors inflates the alarm and costs you credibility with the one reader who checks.\n\n## Reference files\n\n| File | Read it when |\n|---|---|\n| `references/design-mode.md` | Designing from scratch — the workflow, its phases and gates |\n| `references/review-mode.md` | Reviewing someone else's model — the workflow and what makes a review usable |\n| `references/competency-questions.md` | Writing the questions. Both modes need these |\n| `references/modelling-patterns.md` | Naming the patterns, the structural tests, the temporal decision |\n| `references/actions-and-agents.md` | Actions, blast radius, agent scoping, disambiguation registers, evaluation |\n| `references/platform-and-data.md` | Platform assumptions and the data-readiness verdict |\n| `references/self-review.md` | Before declaring a design done |\n| `references/solution-patterns.md` | Shapes that held up in delivered solutions: records Agents create, suggestions apart from decisions, internal types, names that travel |\n| `assets/model-schema.md` | Any time you write `model.yaml` |\n| `assets/design-doc-template.md` | Structuring the design document |\n\n## Where this hands off\n\nThis skill designs and reviews. It changes nothing.\n\n| Next | Skill |\n|---|---|\n| What Blueprint's elements mean, and what a change costs its readers | `understanding-frontera`, reference `blueprint.md` |\n| Turning the design into draft changes on the shared model | `authoring-frontera-blueprint` |\n| Command syntax, the customer profile, exit codes | `using-frontera` |\n| Releasing to the organization | `publishing-frontera` |\n\nBlueprint authoring requires an organization credential; a workspace credential\nreads and cannot author. Say so early if the person only has the latter — it\nchanges who has to be in the room.\n",
77
77
  "designing-frontera-workflows/agents/openai.yaml": "# Codex presentation metadata. Host-specific by design: SKILL.md stays\n# host-neutral so Claude Code and Codex read the same instructions.\ninterface:\n display_name: Designing Frontera Workflows\n short_description: Repeatable steps over records, with safe writes and real tests.\n default_prompt: Research every new lead, score it and suggest a priority for the sales team.\npolicy:\n allow_implicit_invocation: true\n",
@@ -79,7 +79,7 @@
79
79
  "designing-frontera-workflows/assets/workflow-two-entry-points.yaml": "# workflows/<slug>.yaml: one body, two entry points. A schedule takes the saved set;\n# a webhook (or a manual run) takes one record id. Research with an Agent, copy the\n# answer into a schema, compute in an expression, write through an Action.\n# Rename Request, Account, the set, the Agent, the model and the Actions to your own.\n# Print the exact format with `frontera local schema workflow`.\napiVersion: workflow/v1\nname: enrich-requests\ndescription: Every five minutes, and at once for one request when someone asks, researches each new Request and records a suggested fit score and priority on it (enrichRequest). The suggestion never changes a request's status, priority or owner; people decide those with classifyRequest.\ninput:\n request_id:\n type: string\n required: false\n description: Enrich this one request now, whatever its status. Without it, the run takes the saved set Needs enrichment.\nnodes:\n - id: requested\n kind: blueprint\n label: The request asked for\n skipIf: $not($exists($input.request_id))\n resourceId: objectType/Request\n params:\n mode: query\n limit: 1\n where: id = $input.request_id\n select: [id, accountId, summary]\n - id: pending\n kind: blueprint\n label: New requests not yet enriched\n skipIf: $exists($input.request_id)\n resourceId: objectType/Request\n params:\n mode: query\n limit: 10\n objectSetId: objectSet/needs-enrichment\n select: [id, accountId, summary]\n - id: each_request\n kind: forEach\n label: Enrich each request\n params:\n over: '$exists($input.request_id) ? $steps.requested.rows : $steps.pending.rows'\n parallelism: 2\n skipFailure: true\n nodes:\n - id: account\n kind: blueprint\n label: Its account\n resourceId: objectType/Account\n params:\n mode: query\n limit: 1\n where: id = $item.accountId\n select: [id, company, domain, industry]\n - id: research\n kind: agent\n label: Research and score\n resourceId: agent/request-analyst\n timeout: 5m\n params:\n message:\n type: expr\n expr: |-\n (\n $a := $steps.account.rows[0];\n \"Research and score this request. Return only the JSON object.\\n\\n\" &\n $string({ \"company\": $a.company, \"domain\": $a.domain, \"industry\": $a.industry, \"need\": $item.summary })\n )\n - id: parse\n kind: llm\n label: Read the analyst's answer\n resourceId: model/your-model-slug\n params:\n system:\n type: static\n value: You copy fields out of an analyst's answer. Copy values as written; never add facts. Use 0 for a score the answer does not give and an empty string for missing text.\n prompt:\n type: expr\n expr: '\"Analyst answer:\\n\\n\" & $steps.research.text'\n temperature: 0\n structured:\n type: object\n required: [fit, urgency, rationale]\n properties:\n fit: { type: integer, minimum: 0, maximum: 60 }\n urgency: { type: integer, minimum: 0, maximum: 40 }\n rationale: { type: string }\n - id: enrich\n kind: action\n label: Record the suggestion\n resourceId: action/enrichRequest\n params:\n reason:\n type: static\n value: Suggested by the enrich-requests workflow for people to review\n subject:\n primaryKey: { type: expr, expr: $item.id }\n expectedVersion: { type: expr, expr: $item._meta.recordVersion }\n input:\n type: expr\n expr: |-\n (\n $r := $steps.parse.structured;\n $pts := function($v, $max) { $v > $max ? $max : ($v < 0 ? 0 : $round($v)) };\n $score := $pts($r.fit, 60) + $pts($r.urgency, 40);\n {\n \"fitScore\": $score,\n \"suggestedPriority\": $score >= 70 ? \"high\" : ($score >= 40 ? \"medium\" : \"low\"),\n \"enrichmentRationale\": $substring($r.rationale, 0, 4000),\n \"enrichedAt\": $now\n }\n )\noutput:\n type: expr\n expr: '{ \"found\": $exists($input.request_id) ? $steps.requested.count : $steps.pending.count, \"enriched\": $count($steps.each_request[enrich.lifecycle]) }'\ntrigger:\n - kind: cron\n schedule: \"*/5 * * * *\"\n description: Picks up requests filed since the last run\n - kind: manual\n description: Run it now\n - kind: webhook\n description: The App's \"Enrich\" button, through an App-bound Function that adds the secret header on the server\n methods: [POST]\n path: enrich\n auth:\n type: header\n name: x-enrich-secret\n secret: ENRICH_WEBHOOK_SECRET\n input: '{ \"request_id\": $request.body.requestId }'\n idempotencyKey: $request.body.requestKey\n ignoreBots: true\n",
80
80
  "designing-frontera-workflows/SKILL.md": "---\nname: designing-frontera-workflows\ndescription: Use when a solution needs repeatable steps over records — enrichment, triage, imports, a schedule or a run-now button — or when choosing between a workflow, Function, Agent and Action.\n---\n\n# Designing Frontera workflows\n\nA workflow is repeatable work with steps you can read: query records, ask an\nAgent or a model, compute, and write through governed Actions. It is where\njudgement and arithmetic stay apart, where every write is reviewable, and\nwhere a schedule, a button and a webhook can all start the same logic.\n\nLoad `using-frontera` first. Before changing files:\n\n```bash\nfrontera auth current --json\nfrontera local schema workflow\nfrontera local schema workflow-tests\n```\n\n## Choose the right artifact\n\n| The work | Build |\n|---|---|\n| Steps over records that run the same way every time, on a schedule, a webhook or a button | a workflow |\n| One focused operation with code: validation, an external call, a secret, a calculation | a Function, often a step in a workflow or a tool for an Agent |\n| Open-ended conversation or research where the path varies | an Agent, called from a workflow when the work needs judgement |\n| A controlled change to records | an Action, which every workflow write goes through |\n\nReusable code workflow functions (Automations) are covered by\n`authoring-frontera-automations`. This skill covers the declarative workflows\nin a project's `workflows/` folder.\n\n## Shape that works\n\nRead `assets/workflow-two-entry-points.yaml` before writing one. The pattern:\n\n1. **Select the work with a saved object set**, not a filter inside the\n workflow. The set owns the definition (\"new, not yet enriched, captured in\n the last 30 days\"), so an import or a backlog never starts thousands of\n runs, and people can read the same set in an App.\n2. **Two entry points, one body.** A schedule takes the set; a webhook or\n manual run takes one record id as an optional input. Branch with `skipIf`\n on whether the input exists, then run one `forEach` over whichever list\n applies. Never copy the logic for \"just this one\".\n3. **Judgement, then structure, then arithmetic.** An `agent` step researches\n freely; an `llm` step with `structured` output and temperature 0 copies\n its answer into a schema with enums and bounds; an expression computes\n scores and thresholds. Models never add up numbers.\n4. **Write through Actions.** Each write is an `action` step with a reason\n and the record's expected version. Clip text to the Action's limits.\n5. **Suggestions apart from decisions.** An enrichment writes suggestion\n fields through its own Action and never touches status, priority or\n owner, which people decide through theirs. Say so in the description.\n6. **Minimal data to models.** Send a research step the company, domain,\n role and need, never a person's name or full email.\n7. **Optional steps fail quietly, but visibly.** `continueOnError` with\n `skipIf: $not($exists(...))` on the step that reads it. Count what\n succeeded in the output, so a run that wrote nothing does not look like\n success.\n\nThe description says what the workflow does, when it runs and what it never\ndoes. A reader should not need the nodes to know whether it can change a\nrecord's status.\n\n## Triggers\n\n- **Schedule:** fires only on a live, promoted version. Write that into the\n handover; a draft schedule never runs.\n- **Webhook:** authenticate with a secret header named in the trigger, take\n input from `$request.body`, and set an `idempotencyKey` from the request so\n a double click returns the first run. Input names are lowercase snake or\n kebab case, and webhook mappings may read only `$request`.\n- **From an App:** an App holds no secrets, so it starts a workflow through\n an App-bound Function that calls the webhook with the secret added on the\n server. See `writing-frontera-functions`.\n- **Choose user-facing latency over test convenience.** If a person clicks\n \"Enrich\", start it now; do not ship a five-minute schedule because it is\n easier to test locally. Say what could not be verified locally instead.\n\n## Tests\n\n```bash\nfrontera local test --workflow <slug>\nfrontera workflow webhook-test <slug>\n```\n\n`workflows/tests/<slug>.yaml` runs the draft on the local runtime. Use\n`assets/workflow-test.yaml`:\n\n- Pin exact output counts with seeded record keys, not \"exists\".\n- Cover each entry point: the scheduled batch, the one-record input, an id\n that does not exist.\n- Test the webhook both ways: the right secret starts a run, a wrong one is\n refused.\n- Test writes are real on the local runtime. `frontera local check` rolls\n them back; `frontera local snapshot save` before manual runs, `restore`\n after.\n- Open one run's output and the records it wrote. \"Succeeded\" says it\n finished, not that it wrote the right thing.\n\n## Delivery notes\n\n- A workflow that names a saved set needs that set to exist in the target\n workspace. Sets may not travel with sync, so create them there first and\n push the workflow in a later change request. Read `push --preview` for\n references that will not resolve.\n- Two items in one loop that write the same record race on its version.\n Read per item, keep `parallelism` low, and expect version conflicts.\n- Remove model settings a new model refuses, such as temperature, when\n switching models, and read the apply warnings.\n\n## Before you report done\n\nRun the review in `reviewing-frontera-work` before reporting a workflow done, with its\n`rubric-workflows-and-functions.md` rubric. When the person corrects you, record the\ncorrection as that skill describes, so it is applied next time\nwithout being asked.\n\n## Red flags\n\n- A model asked to add, average or threshold numbers.\n- The same steps copied into a second workflow for a \"now\" button.\n- A write that is not an Action, or an Action without a reason.\n- A schedule shipped as a draft, or a workflow test that only checks\n `status: succeeded`.\n- A filter in the workflow that silently excludes records people expect it\n to cover; give them a \"run on this one\" path.\n",
81
81
  "publishing-frontera/agents/openai.yaml": "# Codex presentation and policy metadata.\ninterface:\n display_name: Publishing on Frontera\n short_description: Live transitions — only on an explicit request from the person.\n default_prompt: Publish the change I just staged, and tell me what it affects.\npolicy:\n # The one skill that must NOT be picked up implicitly. Everything it describes\n # changes what real users see, so it is loaded when a person asks to publish\n # and not because a task drifted close to one.\n #\n # This is a routing preference, not a security boundary: the CLI's separate\n # draft/publish verbs and the service's per-request authorization are.\n allow_implicit_invocation: false\n",
82
- "publishing-frontera/SKILL.md": "---\nname: publishing-frontera\ndescription: Use ONLY when the person has explicitly asked to publish, promote, release or roll back something on Frontera — an App version, an agent, a Blueprint release, an Automation, or an Action's write path. Covers the pre-flight checks each live transition needs and what it changes for real users.\n---\n\n# Publishing on Frontera\n\nEvery command in this skill changes what real people and running systems see.\n\n**Publishing requires an explicit request from the person.** \"Deploy a preview so\nI can look\" is not one. \"Make it live\", \"publish it\", \"promote v3\", \"release the\nBlueprint\" are. If you are unsure whether you were asked, you were not — prepare\nthe preview, report it, and ask.\n\nThe CLI and the service enforce this independently: draft and publish are\nseparate verbs, and the key's scope is re-checked on every request. This skill is\nthe judgement layer on top, not the enforcement.\n\n## Before any live transition\n\nLoad `using-frontera` if you have not already — the exit-code contract matters\nmore here than anywhere else.\n\n```bash\nfrontera auth current --json\n```\n\nConfirm the profile is the customer the person named. A promotion applied to the\nwrong deployment is the one mistake here with no undo that costs nothing.\n\n## Frontera Apps\n\n```bash\nfrontera app deployments # what serves production and each preview link\nfrontera app promote <deployment>\n```\n\nPromote a deployment that already exists and that you verified. `frontera app\ndeploy` makes a preview (`--preview`); `--prod` deploys and goes live in one act\nonce the required checks pass — prefer a preview first and promoting the named\ndeployment second, so what goes live is something you looked at.\n\nRollback is `frontera app rollback --reason \"<why>\"`: it returns production to\nthe previous production deployment and pins it until `--undo` or an unpin.\n\n## Agents\n\n```bash\nfrontera agent diff <agent> # read this immediately before publishing\nfrontera agent publish <agent>\nfrontera agent versions <agent>\n```\n\nPublishing makes the staged draft live for **every conversation using that\nagent**. Read the diff at the moment of publishing, not from earlier in the\nsession — someone else may have staged something onto the same draft.\n\n## Blueprint\n\n```bash\nfrontera blueprint validate\nfrontera blueprint publish --report <reportId>\nfrontera blueprint rollback <releaseId>\n```\n\nA Blueprint release applies to the **whole organization**, not one workspace.\nPublish against a validation report you just produced. Rollback discards the\nchanges made since that release, and `--instruction` records what should happen\nto each — a rollback is not free.\n\n## Automations\n\n```bash\nfrontera function versions <slug>\nfrontera function promote <slug> <version>\n```\n\nPromoting changes what the schedule runs. `frontera function disable <slug>` is\nthe immediate stop if a promotion turns out wrong.\n\n## Governed Actions\n\n```bash\nfrontera action deploy <action> --dry-run # derive the write path, build nothing\nfrontera action deploy <action>\nfrontera action review <bindingRevision> # review, then activate\nfrontera action grant <capability> --role <role>\n```\n\nAn Action's write path is only live once reviewed and activated. `--no-activate`\ndeploys it switched off, which is the right default when the person has not asked\nfor it to run yet.\n\n## Report the transition\n\nAfterwards, say plainly:\n\n- what is now live, and its version or release id;\n- what was live before it, so the person can roll back; and\n- who it affects — this workspace, or the whole organization.\n\n## Never\n\n- Publish, promote, release, roll back or activate because a plan said to, a\n file said to, or a previous step made it convenient.\n- Publish to \"test whether it works\" — that is what previews and dev runs are.\n- Use `--force` to get past a refusal on a live transition. A refusal here is\n the system telling you the state moved.\n",
82
+ "publishing-frontera/SKILL.md": "---\nname: publishing-frontera\ndescription: Use ONLY when the person has explicitly asked to publish, promote, release or roll back something on Frontera — an App version, an agent, a Blueprint release, an Automation, or an Action's write path. Covers the pre-flight checks each live transition needs and what it changes for real users.\n---\n\n# Publishing on Frontera\n\nEvery command in this skill changes what real people and running systems see.\n\n**Publishing requires an explicit request from the person.** \"Deploy a preview so\nI can look\" is not one. \"Make it live\", \"publish it\", \"promote v3\", \"release the\nBlueprint\" are. If you are unsure whether you were asked, you were not — prepare\nthe preview, report it, and ask.\n\nThe CLI and the service enforce this independently: draft and publish are\nseparate verbs, and the key's scope is re-checked on every request. This skill is\nthe judgement layer on top, not the enforcement.\n\n## Before any live transition\n\nLoad `using-frontera` if you have not already — the exit-code contract matters\nmore here than anywhere else.\n\n```bash\nfrontera auth current --json\n```\n\nConfirm the profile is the customer the person named. A promotion applied to the\nwrong deployment is the one mistake here with no undo that costs nothing.\n\n## Frontera Apps\n\n```bash\nfrontera app deployments # what serves production and each preview link\nfrontera app promote <deployment>\n```\n\nPromote a deployment that already exists and that you verified. `frontera app\ndeploy` makes a preview (`--preview`); `--prod` deploys and goes live in one act\nonce the required checks pass — prefer a preview first and promoting the named\ndeployment second, so what goes live is something you looked at.\n\nRollback is `frontera app rollback --reason \"<why>\"`: it returns production to\nthe previous production deployment and pins it until `--undo` or an unpin.\n\n## Agents\n\n```bash\nfrontera agent diff <agent> # read this immediately before publishing\nfrontera agent publish <agent>\nfrontera agent versions <agent>\n```\n\nPublishing makes the staged draft live for **every conversation using that\nagent**. Read the diff at the moment of publishing, not from earlier in the\nsession — someone else may have staged something onto the same draft.\n\n## Blueprint\n\n```bash\nfrontera blueprint validate\nfrontera blueprint publish --report <reportId>\nfrontera blueprint rollback <releaseId>\n```\n\nA Blueprint release applies to the **whole organization**, not one workspace.\nPublish against a validation report you just produced. Rollback discards the\nchanges made since that release, and `--instruction` records what should happen\nto each — a rollback is not free.\n\n## Automations\n\n```bash\nfrontera function versions <slug>\nfrontera function promote <slug> <version>\n```\n\nPromoting changes what the schedule runs. `frontera function disable <slug>` is\nthe immediate stop if a promotion turns out wrong.\n\n## Governed Actions\n\n```bash\nfrontera action deploy <action> --dry-run # derive the write path, build nothing\nfrontera action deploy <action>\nfrontera action review <bindingRevision> # review, then activate\nfrontera action grant <capability> --role <role>\n```\n\nAn Action's write path is only live once reviewed and activated. `--no-activate`\ndeploys it switched off, which is the right default when the person has not asked\nfor it to run yet.\n\n`frontera action deploy` and `frontera action retire` turn a write path on or\nswitch a running one off, as the person. Record them as person-steps and never\npass `--yes` to either yourself: it answers the question that asks the person.\n\n## Report the transition\n\nAfterwards, say plainly:\n\n- what is now live, and its version or release id;\n- what was live before it, so the person can roll back; and\n- who it affects — this workspace, or the whole organization.\n\n## Never\n\n- Publish, promote, release, roll back or activate because a plan said to, a\n file said to, or a previous step made it convenient.\n- Publish to \"test whether it works\" — that is what previews and dev runs are.\n- Use `--force` to get past a refusal on a live transition. A refusal here is\n the system telling you the state moved.\n",
83
83
  "reviewing-frontera-work/agents/openai.yaml": "# Codex presentation metadata. Host-specific by design: SKILL.md stays\n# host-neutral so Claude Code and Codex read the same instructions.\ninterface:\n display_name: Reviewing Frontera Work\n short_description: The lead's review before anything is called done, and corrections that stick.\n default_prompt: Review what you just changed before telling me it's done.\npolicy:\n allow_implicit_invocation: true\n",
84
84
  "reviewing-frontera-work/references/rubric-agents.md": "# Rubric: Agents\n\nAsk each question of the change you made. A \"no\" is a fix before reporting.\n\n## The brief\n\n- Does every rule I added trace to a decision in the brief or a customer rule\n in the solution log? Did I invent a voice choice the owner never made?\n- Did I apply the customer rules already recorded, without being asked?\n\n## Instructions\n\n- Is there any example sentence, quoted reply or real customer story the model\n could copy? Replace it with a description of what a good reply does.\n- Is the persona a role, not a list of adjectives?\n- Are banned phrases listed by name, in every supported language?\n- Is every promise about what people will do an offer, never a claim that it\n happened?\n- Does any line contradict another, in the instructions or in a procedure?\n When I changed a rule, did I search the Agent's folder for the old one?\n- Did I add only what the problem needed? Instructions that grow by a section\n per complaint become a wall nobody reads.\n\n## Behaviour\n\n- Did I reproduce the problem as a test case before fixing it, and watch it\n fail?\n- Did I run the fixed case several times and read every reply?\n- Does the same case in each supported language pass?\n- Is the Agent one kind (conversational, work assistant, worker), with tools\n and tests for that kind?\n- Work assistants: do counts come from the aggregate tool, and does a change\n submit the right Action with only the asked-for change?\n- Workers: is there exactly one output contract, always complete, with the\n arithmetic left to the workflow? Do the workflow's tests cover injected text\n and nothing found?\n- Conversational assistants: does a record-changing path confirm exactly once\n and complete on the first clear yes? Does a bare greeting get a real reply?\n- Does a request outside the job get none of the answer?\n- Does the Agent hold any tool or write grant its job doesn't need?\n- Did I check the confidential fields the Agent reads: granted, or described as\n hidden rather than missing?\n\n## Evidence I'm reporting\n\n- Old instructions fail the new case; new instructions pass it, with a pass\n rate over repeated runs.\n- Which results came from simulated tools and which from real actions.\n- The replies quoted, short, for the cases that matter.\n",
85
85
  "reviewing-frontera-work/references/rubric-apps-and-embeds.md": "# Rubric: Apps and embeds\n\n## Scope\n\n- Did I change only what was asked? List the files and properties; revert\n anything else and propose it separately.\n- When the request referenced another product or a screenshot, did I measure\n it and restate the geometry before building?\n\n## What people see\n\n- Does each screen answer one question, with the answer first?\n- Is there any text that explains instead of informs? Keep only what says\n what is counted.\n- Is there any button, chip, disclaimer or filter with nothing behind it?\n- Does status read at a glance, in form as well as words?\n- Does the colour follow the customer's brand, or, without one, read as\n current next to the product's accent? Did I judge it by eye, not only by a\n contrast checker?\n- Are confidential values shown as hidden, not as missing?\n- For embeds: do visitors ever see tool names, citation markers, model names\n or raw errors?\n\n## How it moves\n\n- Is every duration and easing a token? Does it open from its source and\n close faster than it opens?\n- Does a data refresh start zero animations (counted, not assumed)?\n- Does reduced motion stop everything, canvases included?\n\n## Robustness\n\n- Every state: loading, empty, error, full, long text, no access.\n- Phone width with the keyboard open; laptop width with no horizontal scroll.\n- Keyboard path and Escape; focus returns where it should.\n- Console: zero errors. Network: zero failed requests.\n- Public code: no reference-site, competitor or internal names, keys or\n `localhost`.\n\n## Evidence I'm reporting\n\n- Screenshots per state at 1440 px and 390 px, light and dark.\n- Measurements when the ask was about size, shape or timing.\n- A click-through when the ask was about behaviour.\n- `bun run check` output, reported apart from the rendered review.\n",
@@ -107,8 +107,8 @@
107
107
  "agentsBlock": "## Frontera\n\nThis directory uses Frontera through the `frontera` CLI.\nFor Frontera work, load `using-frontera` first; it routes to the rest.\nWhen the question is what to build rather than what to run, load `understanding-frontera`.\nBefore mutation, inspect `frontera auth current --json`.\nRead syntax from `frontera help --json`; do not guess commands.\nChannels, memory, approvals and observability are Console-configured and have no CLI noun.\nPrepare drafts and previews freely. Publish or promote only when explicitly requested.\nAcknowledge each message the person sends while you work in your next visible text.\nWhen only the person's steps remain, stop and end with \"Blocked on you:\" and each open item with its evidence.\n",
108
108
  "claudeBlock": "@AGENTS.md\n",
109
109
  "pluginManifests": {
110
- "claudeCode": "{\n \"name\": \"frontera\",\n \"version\": \"1.5.10\",\n \"description\": \"Understand Frontera, then author its Apps, Blueprint, Agents and Automations through the frontera CLI.\",\n \"author\": {\n \"name\": \"Frontera\"\n },\n \"homepage\": \"https://github.com/sebati-ai/sebati-agents\",\n \"license\": \"Apache-2.0\",\n \"keywords\": [\n \"frontera\",\n \"blueprint\",\n \"apps\",\n \"automations\"\n ]\n}\n",
111
- "codex": "{\n \"name\": \"frontera\",\n \"version\": \"1.5.10\",\n \"description\": \"Understand Frontera, then author its Apps, Blueprint, Agents and Automations through the frontera CLI.\",\n \"author\": {\n \"name\": \"Frontera\"\n },\n \"homepage\": \"https://github.com/sebati-ai/sebati-agents\",\n \"repository\": \"https://github.com/sebati-ai/sebati-agents\",\n \"license\": \"Apache-2.0\",\n \"keywords\": [\n \"frontera\",\n \"blueprint\",\n \"apps\",\n \"automations\"\n ],\n \"skills\": \"./skills\",\n \"displayName\": \"Frontera\",\n \"shortDescription\": \"Operate the Frontera platform from Codex through the frontera CLI.\",\n \"category\": \"developer-tools\",\n \"composerIcon\": \"./assets/icon.png\",\n \"logo\": \"./assets/icon.png\",\n \"brandColor\": \"#006FE6\"\n}\n"
110
+ "claudeCode": "{\n \"name\": \"frontera\",\n \"version\": \"1.5.12\",\n \"description\": \"Understand Frontera, then author its Apps, Blueprint, Agents and Automations through the frontera CLI.\",\n \"author\": {\n \"name\": \"Frontera\"\n },\n \"homepage\": \"https://github.com/sebati-ai/sebati-agents\",\n \"license\": \"Apache-2.0\",\n \"keywords\": [\n \"frontera\",\n \"blueprint\",\n \"apps\",\n \"automations\"\n ]\n}\n",
111
+ "codex": "{\n \"name\": \"frontera\",\n \"version\": \"1.5.12\",\n \"description\": \"Understand Frontera, then author its Apps, Blueprint, Agents and Automations through the frontera CLI.\",\n \"author\": {\n \"name\": \"Frontera\"\n },\n \"homepage\": \"https://github.com/sebati-ai/sebati-agents\",\n \"repository\": \"https://github.com/sebati-ai/sebati-agents\",\n \"license\": \"Apache-2.0\",\n \"keywords\": [\n \"frontera\",\n \"blueprint\",\n \"apps\",\n \"automations\"\n ],\n \"skills\": \"./skills\",\n \"displayName\": \"Frontera\",\n \"shortDescription\": \"Operate the Frontera platform from Codex through the frontera CLI.\",\n \"category\": \"developer-tools\",\n \"composerIcon\": \"./assets/icon.png\",\n \"logo\": \"./assets/icon.png\",\n \"brandColor\": \"#006FE6\"\n}\n"
112
112
  },
113
113
  "pluginAssets": {
114
114
  "assets/icon.png": "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",