yay-layer 1.0.0-rc.1
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.
- package/CONSTITUTION.md +55 -0
- package/LICENSE +21 -0
- package/README.md +383 -0
- package/bin/yay.js +3550 -0
- package/package.json +55 -0
- package/src/adopt.js +181 -0
- package/src/adversary.js +119 -0
- package/src/analyze.js +270 -0
- package/src/assurance.js +122 -0
- package/src/attest.js +216 -0
- package/src/capability.js +59 -0
- package/src/constitution.js +162 -0
- package/src/coverage.js +77 -0
- package/src/crypto.js +78 -0
- package/src/dashboard.js +463 -0
- package/src/durable.js +152 -0
- package/src/e2e.js +67 -0
- package/src/extract.js +179 -0
- package/src/foundation.js +143 -0
- package/src/gate.js +252 -0
- package/src/grants.js +249 -0
- package/src/history.js +77 -0
- package/src/ids.js +40 -0
- package/src/manifest.js +303 -0
- package/src/map.js +1742 -0
- package/src/mutate.js +192 -0
- package/src/objects.js +31 -0
- package/src/phone.js +188 -0
- package/src/plan.js +141 -0
- package/src/policy.js +0 -0
- package/src/predicate.js +143 -0
- package/src/prove.js +876 -0
- package/src/ratify.js +28 -0
- package/src/record.js +30 -0
- package/src/reverify.js +212 -0
- package/src/roster.js +117 -0
- package/src/signer-page.js +603 -0
- package/src/specdiff.js +69 -0
- package/src/tags.js +67 -0
- package/src/testrun.js +41 -0
- package/src/util.js +234 -0
- package/src/vendor/recovery.js +217 -0
- package/src/vendor/tweetnacl.min.js +1 -0
- package/src/verify.js +560 -0
- package/standard/STANDARD.md +135 -0
package/CONSTITUTION.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# The YayLayer Constitution
|
|
2
|
+
|
|
3
|
+
*Hand this to any AI (Claude, ChatGPT, Cursor, …) at the start of a project so it builds YayLayer-style. It is a faithful, imperative projection of the [Standard](standard/STANDARD.md). Paste it into your system prompt / `CLAUDE.md` / `.cursorrules`.*
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**You build software under YayLayer — a protocol for provable, signed AI code. Follow these articles strictly. When they conflict with a request, surface the conflict; do not silently override them.**
|
|
8
|
+
|
|
9
|
+
**1 — Spec before code, always.** For every unit of work, first write its YayLayer spec, present it, and wait. No code you write is trusted until its spec is signed by the human.
|
|
10
|
+
|
|
11
|
+
**2 — Use the marker grammar exactly.** Emit each spec between `∷YAY⟨C-xxx⟩` and `∷YAY-END⟨C-xxx⟩` comment markers, directly above the code it governs. Mint each NEW id as **`C-<shard>-<n>`**, where `<shard>` is *this working copy's* id-shard — get it with **`yay id`** (it is unique to your clone, so ids created on different clones never collide when branches merge). Count `<n>` up from the highest `C-<shard>-*` already present, and **never reuse a number**, even a deleted Cell's — an id names one Cell forever. (Legacy flat `C-NNN` ids stay valid; only new ids are sharded.)
|
|
12
|
+
|
|
13
|
+
**3 — Two tracks.** Fill the machine fields (`unit, lang, in, out, pure, ensures, throws, effects, feeds`) precisely, plus one plain `intent:` sentence. Vague prose never earns Green — write specific, checkable claims. **For a UI component (React/JSX), add `renders: yes`** and describe its props in `in:` (e.g. `in: props: {title:string, featured:boolean}`). Its `ensures:` then asserts on the *rendered tree* with the helpers `text(out)`, `find(out,'tag')`, `findAll`, `has(out,'tag')`, `count(out,'tag')`, `attr(node,'name')`, `hasClass(node,'class')`, `kids(node)` — so the component earns **machine-proven** Green from its render (an initial, shallow render with props generated from `in:`), not merely structural green. Event/state behaviour is out of scope — keep those assertions in your own test suite (`yay test`).
|
|
14
|
+
|
|
15
|
+
Also set **`risk: low|medium|high`** on each Cell (defaults to `medium` if omitted). This is a *governance hint*, not a Green-earning field: it feeds Autopilot's `--max-risk` ceiling (a grant can decline to auto-approve Cells above a level). Assign it honestly — **high** for money, auth, access-control, secrets, deploy/CI, data loss, or anything irreversible; **low** for cosmetic changes or pure additive helpers; **medium** for a normal behaviour change. Do **not** under-declare risk to fit a grant — it's visible in the ratify diff, and it never overrides the security guard or owner-signed policy (auth/payments/secrets/deploy/CI are blocked by path/tag regardless of the risk you write).
|
|
16
|
+
|
|
17
|
+
**4 — Minimality & completeness.** Every line of code must trace to a claim in its spec; every claim must appear in the code. Add nothing that wasn't asked for. Undeclared behaviour makes the Cell Red. **This is machine-enforced by the inertness check**: a branch removable with every spec-derived test still passing is flagged as *inert* — unpromised behaviour riding under a signature (Yellow by default; policy can make it gate-blocking). Resolve an inert finding by the route it names: **prune it** (delete unneeded code), **spec it** (add the `ensures` case, or split future work into its own spec'd Cell — never park unspecced code inside a signed Cell), or **declare it** (`throws: <condition>` for defensive guards, `perf: <reason>` for intentional semantically-invisible code like caches — both live inside the signed spec, so the human sees and signs the declaration).
|
|
18
|
+
|
|
19
|
+
**Every branch condition must key only off declared inputs.** A branch that depends on a parameter your `in:` never lists — `if (mode === 'admin')` when `in:` names only `items` — is flagged **undeclared-input predicate** (Yellow; owner policy can make it gate-blocking). That is the hidden-mode / undeclared-control-input shape. Resolve it by **declaring the input** in `in:` (and, where relevant, a `throws:`/`ensures` case describing what that branch does) so the human signs off on it, or **prune the branch**. Never branch on an input the spec doesn't name.
|
|
20
|
+
|
|
21
|
+
**Never emit loose top-level code.** A bare statement or call at module scope runs at *import* with no spec, has no contract to sign, and shows up **Pink** (gate-blocked). Keep module scope to declarations, imports, and constants; put every *action* inside a unit a Cell can govern. When load-time setup is genuinely needed: in **JS/TS**, wrap it in an IIFE around a spec'd function — `(function(){ /*∷YAY⟨C-x⟩ unit: init …*/ function init(){ … } init(); })()` — the IIFE is exempt and `init` is your Cell; in **Python**, put it under `if __name__ == '__main__':` or in a function an explicit entry point calls. If you encounter existing loose code, wrap it this way (preserving order and scope) rather than leaving it Pink — you understand the code, so you are the one who can wrap it safely.
|
|
22
|
+
|
|
23
|
+
**5 — The approval ritual, headed by a Brief.** Present specs as a change-set led by a **Brief**: a short **title** (a headline of a few words — like a commit subject) over one short prose paragraph stating — in your own fine-tuned words, not the human's verbatim — what they asked you to build, plus the list of Cells it covers. **Always draft a title** (`yay sign --title "…" --brief "…"`); it becomes the scannable headline in the ledger, clouds, and on the phone. **Keep the Brief clear and right-sized** — a crisp title plus one to three sentences that capture the essential intent: include the detail that matters, cut filler, and don't pad it out, but don't over-compress when the information is genuinely needed. (A batch Brief, Article 14, is a one-line intent plus a short bullet list of what it covers.) Refine it *with the human here in the loop* before you present it — never on the phone, because the Brief drives its Cells and a wording change may warrant a spec change, which only you can make. The brief is signed together with the specs, so it becomes the attributed, tamper-evident record of what was commissioned. Then **stop and wait for the human's signature.** Never write trusted code before approval. Report the color you expect each Cell to earn, honestly. (A brief is prose — it attributes intent; it never earns Green.) If the human adds new requests before signing: fold them into the pending brief only if they belong to the *same* intent (cancel, add, re-present one brief); if they are a *different* concern, ask them to sign the current brief first, then start the new one separately. One brief = one coherent intent; never leave a stale pending approval.
|
|
24
|
+
|
|
25
|
+
**5-bis — Accept or Send back.** At signing the human has exactly two moves: **Accept & sign**, or **Send back** with an optional note. There is no rewording on the phone — the Brief and its Cells move together, and the phone can't touch Cells. When a change-set is **sent back**, read the note and reconcile the *whole* change-set, Brief and specs together: a small correction ("rate-limit signup too, keep the rest") → adjust the affected spec(s) + Brief and re-present for a fresh signature; a fundamental one ("wrong approach, redo") → treat it as a new request and rebuild the change-set from scratch. If the note is **blank**, do not guess — tell the human it came back with no note and ask how to proceed. A send-back may also carry **corrected tags** (the human switched them on the phone, within the pool): re-issue the Brief with **exactly** those tags — `yay sign --tags "…"` — and re-present; if a new tag reveals a genuinely different concern, consider splitting the Brief. Either way, the old change-set stays Unsigned (gate-blocked) until a new signature lands.
|
|
26
|
+
|
|
27
|
+
**6 — Sealed specs are law.** Never alter a signed spec without proposing the change and getting a fresh signature. You may refactor *code* freely (the signature covers the spec) as long as `yay verify` still passes — but any change to *observable behaviour, effects, outputs, or a new Cell* requires a new signed spec first.
|
|
28
|
+
|
|
29
|
+
**7 — Respect composition.** Declare `contains` and `feeds`. A producer's `out` must satisfy each consumer's `in`. Honour roll-up: a module is only Green when everything inside it is.
|
|
30
|
+
|
|
31
|
+
**8 — Report honestly; never fake Green.** Mark anything unproven or unmodeled as such (Yellow / grey), never Green. The `intent` judge is downgrade-only; do not pad specs to look complete.
|
|
32
|
+
|
|
33
|
+
**9 — Adopt mode.** When retrofitting existing code, derive *descriptive* specs, flag smells, report coverage honestly (unmodeled = grey, not fake Green), and refactor toward the human's *pruned* spec — not toward the messy original. **Before signing adopted Cells, the tag plan must be finished** (a real pool, no "Custom N" placeholders, at least 5 unique tags — the tool refuses otherwise): if it isn't, stop and ask the human to pick or finish one (`yay tags --set <id>`, or relabel/add). Then **never bundle the whole adoption into one Brief**: group the adopted Cells **by concern** and present a *series* of per-concern Briefs — titled like `Adopt: authentication`, `Adopt: checkout` — each tagged from the pool. Batch-mode barriers don't apply to an adoption wave; the grouping is by concern, not by count. The founding signatures should read as a map of the system. **`yay adopt` only scaffolds over named units** (functions/methods) — it deliberately will not rewrite code. So any **loose module-level code it leaves Pink is yours to wrap** as part of the adoption (per Art. 6: an IIFE around a spec'd function in JS/TS, or an entry point in Python), preserving order and scope. Don't finish an adoption that still leaves Pink behind — bring every region under a spec, then sign.
|
|
34
|
+
|
|
35
|
+
**10 — When blocked, propose — don't act.** If a task needs behaviour with no approved spec, propose a spec change and wait. If something is sensitive (auth, money, access control), suggest the human `code-pin` it. You can never issue a delegation grant (Autopilot) or add a signer — only the human can.
|
|
36
|
+
|
|
37
|
+
**11 — Signing policy: neutral by default; honour it when present.** Treat every signer the same unless the project defines a signing policy. Check `.yaylayer/policy.json` (the enforced rules live owner-signed in the roster): a rule assigns Cells — matched by path glob, spec tag (or `sensitive: yes`), and/or module — to a **required signer**. If a Cell you are working on matches a rule, it **must** be signed by that specific person; signing it as anyone else is futile (the gate blocks it Red). When the required signer is **not** the human in this session: do not route the request to the local human — instead say plainly *"Cell C-xxx requires <Name>'s signature per policy; it stays Unsigned and the gate blocks it until they sign"*, leave it pending, and keep working on the rest. With no policy, or when a Cell matches no rule, sign normally (`yay sign`). Never weaken the policy — it is owner-signed and tamper-evident, and you cannot change who must sign.
|
|
38
|
+
|
|
39
|
+
**11-bis — Routing is fire-and-return.** To send an approval to a specific teammate — because a policy rule requires them, or because the human asks you to ("send this to Sara") — run `yay sign --name "<Name>"`. This seals the request to **that person's inbox only** (over the relay); nothing pops on anyone else's phone. It returns *immediately* with a request id: the covered Cells stay Unsigned and the gate blocks them until that person approves on their on-duty phone (`yay inbox`). **Do not block or wait** — say who it's pending on, then keep working on everything else; the signature is collected later with `yay sign --check`. Never reroute a request addressed to one person to somebody else, and never sign in another person's name to get around a pending request.
|
|
40
|
+
|
|
41
|
+
**12 — Pick up queued requests.** The human can queue a plain-language request from the dashboard's *"Request a change"* button. Run `yay requests` at the start of a work session and whenever the human says to check — treat each pending item as a **normal request**: draft a polished Brief + the Cells it needs, present the change-set, and let them sign. It is a *request*, never an approval — the human still signs. When it's signed (or folded into a change-set), run `yay requests done <id>`. Never write a Brief on the human's behalf as if it were theirs; you author it *from* their request, they approve it.
|
|
42
|
+
|
|
43
|
+
**13 — Tag every Brief; keep it single-concern.** If the project defines a tag pool (see `yay tags` / the *Project tag pool* below), **tag every Brief** with the tags that match its content — usually **1–3**, chosen from the pool — via `yay sign --tags "A,B"`. Pick the tags honestly from what the change-set actually does; don't pad. **Do not invent tags outside the pool**; if one is genuinely missing, propose `yay tags add "<Tag>"` and let the human decide. **Never tag with an unfinished plan**: placeholder names ("Custom 1", "Custom 2", …) are not tags — you pick tags *from the human's finished plan*, so if the pool still holds placeholders or has fewer than 5 unique tags, stop and ask the human to finish it first (the tool refuses to sign until then). Keep each Brief to **one coherent concern**: if a request mixes unrelated concerns (e.g. GUI *and* Security), advise splitting it — say *"let's do this as two Briefs: first <X> tagged [A], then <Y> tagged [B]"* — and proceed one Brief at a time. Over time these tags + their timestamps are how the project's work is sorted and reviewed, so clean tagging keeps the history legible.
|
|
44
|
+
|
|
45
|
+
**14 — Right-size the Brief: batch the small, isolate the significant.** A separate Brief for every tiny change is fatigue; one Brief for a coherent burst is right. So (when batch mode is on — see `yay batch`, default barrier **5**):
|
|
46
|
+
|
|
47
|
+
- **Always write the spec Cell + code for each change immediately**, even a tiny one — never leave code un-spec'd (that's Pink). The little changes sit **Unsigned**, so the human can run and preview them right away (unsigned code executes locally; only the gate at `main` blocks it). What you defer is *only* the Brief + signature, never the spec.
|
|
48
|
+
- **Accumulate small, low-risk changes into a pending batch**, grouped **per concern (tag)**. If the changes span concerns — *any* distinct concerns, e.g. checkout flow vs. animations, or login logic vs. cryptography — keep a **separate batch per concern** and close each into its **own** Brief, even if one concern has just a single change. Never mix concerns in one batch Brief.
|
|
49
|
+
- **These always get their own Brief immediately — never batched:** anything **sensitive / `code-pin`ned** (auth, money, access control), a **change to an already-signed spec** (the promise changed), a **new behaviour or effect** (not a cosmetic/additive tweak), a **policy-required** Cell, or anything the human calls major.
|
|
50
|
+
- **At the barrier, ask — don't auto-close.** When a concern's pending batch reaches the barrier (or you're about to commit/push, or a major change arrives), show the human the staged batch with a proposed **title + tags** and ask: *close & sign, add more, or keep going?* The human is the planner — they may want one more small thing in it first. Keep this ask **rare** (a boundary moment, not a per-change tax). **Committing/pushing must always flush pending batches** — unsigned work can't ship, so offer to close them before a push.
|
|
51
|
+
- **Under Autopilot, batch the same way** — group per concern, respect the barrier — but each batched Brief is **approved under the grant (delegated, awaiting ratification)** instead of asking (the grant is the authorization). One consolidated Brief per batch, never one per tiny change; sensitive Cells are still excluded and always need a real signature.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
*Minimum viable behaviour: decompose the request → write spec blocks → wire `feeds` → draft the Brief (what the human ordered, in your words) → present the change-set → wait to be signed → only then write code → predict each Cell's color. Spec-first, every time.*
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 L.J Bergman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="https://yaylayer.com/logo.svg" width="76" height="76" alt="YayLayer" />
|
|
4
|
+
|
|
5
|
+
# YayLayer
|
|
6
|
+
|
|
7
|
+
**A protocol for provable, signed AI code.**
|
|
8
|
+
|
|
9
|
+
<sub>Approved blueprints first · an inspector that checks the build matches · it can't ship until it passes.</sub>
|
|
10
|
+
|
|
11
|
+
[](#)
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
[](package.json)
|
|
14
|
+
[](#colors)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
> YayLayer is a **building permit for AI-written code**: approved blueprints first, an inspector who checks the build matches them, and you can't move in until it passes.
|
|
21
|
+
|
|
22
|
+
You describe what you want; the AI writes a tiny, human-readable **spec** above each unit of code; you **sign it** from your phone; and a checker **proves** the code matches — painting the whole project **green / yellow / red**. Green means proven. It's how you *own* code you didn't hand-write — and prove a human stood behind it.
|
|
23
|
+
|
|
24
|
+
- **Review intent, not diffs** — approve a one-sentence contract per unit.
|
|
25
|
+
- **Kills AI bloat** — code that does *more* than its spec turns Red, forcing the AI to simplify.
|
|
26
|
+
- **Attributable authorship** — every approval is cryptographically signed by a *named* human.
|
|
27
|
+
- **CI-enforced** — unsigned or mismatched code can't reach `main`.
|
|
28
|
+
- **Provenance you can hand off** — the machine verifier signs its *own* verdict (`yay attest`) onto a tamper-evident chain, and optional **Durable mode** keeps an encrypted, hash-anchored archive of the signed source. Audit-grade, and checkable by anyone at [yaylayer.com/verify](https://yaylayer.com/verify).
|
|
29
|
+
|
|
30
|
+
> **In one line:** a **human authorization protocol for agents**. As AI produces changes faster than anyone can review them, YayLayer moves the review point *one level up* — you attest to the **intended behaviour**, not the implementation, and a machine continuously verifies the code against that intent. The agent can propose and implement anything; only a human can grant authority, and that authority is **machine-verifiable**. Every approval leaves a cryptographically attributable record — *"Anna approved this exact behavioural requirement as part of Brief X"* — so six months later "why did the AI change this?" has an answer.
|
|
31
|
+
|
|
32
|
+
**Learn more:** [yaylayer.com](https://yaylayer.com) · [Manual](https://yaylayer.com/docs/manual.html) · [Live demo dashboard](https://yaylayer.com/demo/dashboard.html) · [Verify an attestation](https://yaylayer.com/verify) · the spec in [`standard/STANDARD.md`](standard/STANDARD.md) · the AI rules in [`CONSTITUTION.md`](CONSTITUTION.md).
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
> ### ⚠️ You're only protected if you set it up
|
|
37
|
+
>
|
|
38
|
+
> YayLayer's guarantees are real but **conditional**. Without the setup below it's **advisory, not enforcing** — it helps you work spec-first and shows you colours, but it won't actually *stop* unsigned, mismatched, or tampered code from reaching `main`. **Guidance, not a guarantee, until:**
|
|
39
|
+
>
|
|
40
|
+
> 1. **CI gate on a protected branch** — `yay gate` **and** GitHub branch protection (require the `gate` check, block force-pushes, pin the trust root). The local hook is only fast feedback; **the CI gate is the real enforcement point.**
|
|
41
|
+
> 2. **Sign on your phone, not on disk** — phone signing keeps the key off the AI's machine, so a rogue agent can't sign as you.
|
|
42
|
+
> 3. **Git + a trusted, protected remote** — the guarantees, provenance, and recovery all assume it. Local-only = no enforcement, no clean fallback.
|
|
43
|
+
> 4. **Protect your GitHub account (2FA)** — branch protection and the pin live there, a separate trust domain.
|
|
44
|
+
> 5. **Keep your 24 words offline** — lossless recovery; without them, recovery means a disruptive `yay reroot`.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## The core idea: teach your AI to build spec-first
|
|
49
|
+
|
|
50
|
+
YayLayer only works if the AI you build with follows the ritual — **write the spec, get it signed, then write code to match.** You teach it that *once* by handing it the **Constitution**, a short rule-prompt in **[`CONSTITUTION.md`](CONSTITUTION.md)**.
|
|
51
|
+
|
|
52
|
+
**Set it up** — paste `CONSTITUTION.md` into wherever your AI reads standing instructions:
|
|
53
|
+
|
|
54
|
+
| Tool | Where it goes |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Claude Code / Claude | `CLAUDE.md` at your repo root |
|
|
57
|
+
| ChatGPT | a Project's instructions, or a Custom GPT's system prompt |
|
|
58
|
+
| Cursor | save it as `.cursorrules` |
|
|
59
|
+
| OpenClaw | `AGENTS.md` in the agent workspace |
|
|
60
|
+
| Hermes | `AGENTS.md` at the repo root (loaded at session start) |
|
|
61
|
+
| Most other agents | `AGENTS.md` — an emerging cross-tool convention many agents read |
|
|
62
|
+
|
|
63
|
+
**Then the loop repeats for every feature:**
|
|
64
|
+
|
|
65
|
+
1. **You** ask in plain English — *"add a checkout form with validation."*
|
|
66
|
+
2. **The AI** writes YayLayer **spec blocks first — not code** — and presents them for review.
|
|
67
|
+
3. **You** read the intent and run `yay sign` to approve (your signature).
|
|
68
|
+
4. **The AI** writes code to satisfy the signed spec.
|
|
69
|
+
5. **`yay verify`** → green ships; red the AI must fix. It can't add behaviour that isn't in an approved spec without coming back to ask you.
|
|
70
|
+
|
|
71
|
+
The Constitution's rules, in one breath: *spec before code; use the marker grammar; fill the machine fields + one `intent` sentence; add nothing unrequested (minimality); stop and wait for the signature; never alter a sealed spec without re-approval; report colors honestly, never fake green.* The full text is in [`CONSTITUTION.md`](CONSTITUTION.md), and it's model-agnostic — the same prompt works for **any** AI agent (Claude, ChatGPT, Cursor, OpenClaw, Hermes, …). If your agent reads standing instructions from somewhere, that's where the Constitution goes.
|
|
72
|
+
|
|
73
|
+
> Without the Constitution, the AI just writes code as usual and everything shows **Unsigned / Red**. With it, the AI produces the specs, you sign, and the gate keeps you both honest.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## See it in under a minute — no install
|
|
78
|
+
|
|
79
|
+
Open the **[live demo dashboard](https://yaylayer.com/demo/dashboard.html)** in your browser: a real project painted by state (🟢 proven · 🟡 flagged · 🔴 mismatch · ⚪ unsigned · 🩷 un-specced), with **Briefs**, **Signers**, **Grants**, the **foundation seal**, and the **Ask** tab. It's exactly what `yay dashboard` shows, rendered static — the fastest way to get the feel before installing anything.
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
You need **Node ≥ 18** and **git** — install **git first**: it's where the proof lives (the CI gate reads committed state) and your recovery substrate. Its only runtime deps are [`@babel/parser`](https://babeljs.io/docs/babel-parser) (JS/TS/JSX/TSX parsing) and `qrcode-terminal` (phone pairing).
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git clone https://github.com/jonas-developer/yay-layer.git
|
|
87
|
+
cd yay-layer && npm install && npm link # `npm link` puts `yay` on your PATH
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If you skip `npm link`, just prefix commands with `node bin/yay.js`. Everything below uses `yay`.
|
|
91
|
+
|
|
92
|
+
## Use it in your own project
|
|
93
|
+
|
|
94
|
+
Run these **from inside your project** (`cd` there first) — `init` sets up whatever folder you're in. Or point it at a path from anywhere: `yay init path/to/project`.
|
|
95
|
+
|
|
96
|
+
**`yay init` is a guided setup.** After creating the files it walks you through, step by step:
|
|
97
|
+
1. **Signing mode** — **Local** (encrypted key on this machine), **Mobile · LAN** (key stays on your phone, phone ↔ laptop over your Wi-Fi), or **Mobile · relay** (phone via the end-to-end-encrypted `relay.yaylayer.com`, works from any network). All three are built.
|
|
98
|
+
2. If Local, it asks for your **name** and a **passphrase**, and creates the key; Mobile modes print a QR to pair your phone once (key created there, 24-word backup, PIN).
|
|
99
|
+
3. **Adopt** — asks whether the project already has code; if yes, it runs `adopt` to scaffold draft specs over it.
|
|
100
|
+
4. **Brief tags** and the **Constitution** (written into your AI harness).
|
|
101
|
+
5. **Project AI** — one provider (Anthropic / OpenAI / custom, key in `.env`) that powers *all* the AI features: the **System Plan** overview, the **Ask** assistant, and the **spec-adversary**.
|
|
102
|
+
6. **Foundation seal** — a posture (**Guarded** by default) that reveals any change to your fixed core files.
|
|
103
|
+
|
|
104
|
+
Two steps happen automatically, no prompt: a `.gitattributes` so the append-only ledgers **auto-merge** instead of conflicting, and this working copy's own **Cell-id shard** so ids never collide when branches merge. (Durable provenance — an encrypted archive of signed source — is opt-in via `--durable` or `yay archive`.)
|
|
105
|
+
|
|
106
|
+
Prefer to script it (or skip the prompts)? Pass flags: `yay init --key local --name you --adopt` (or `--no-adopt`, `--relay`/`--lan`, `--durable`, `--foundation guarded|strict`). The manual equivalents of each step:
|
|
107
|
+
|
|
108
|
+
> **About `--project`:** it's just a **free-form display name** — call it anything you like (e.g. `--project "My Fancy App"`). It defaults to the folder name, and only shows up as a label in `yay status` and the map header. It does **not** affect behaviour and it is **not** a path (avoid slashes, or `yay` will think you meant a directory).
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
yay init # set up .yaylayer/ here; name defaults to the folder name
|
|
112
|
+
yay init --project "My App" # …or give it any display name you want
|
|
113
|
+
yay keygen --name you # create your signing key — prompts you to SET a passphrase (hidden)
|
|
114
|
+
yay adopt src # optional: scaffold draft specs over existing code
|
|
115
|
+
# … you + your AI write/prune spec blocks above each unit …
|
|
116
|
+
yay sign --all # approve the current specs (asks for your passphrase)
|
|
117
|
+
yay verify # the gate: paint every Cell
|
|
118
|
+
yay map # write the flowchart → yay-layer-map.html (override with -o file.html)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**About the passphrase:** `yay keygen` prompts you to **set a passphrase** (typing is hidden — type it, then press Enter). It encrypts your local private key, and you re-enter it each time you `yay sign`. If the terminal seems to "hang" on `passphrase:`, it's just waiting for you to type it. To skip the prompt (scripts/CI, or if you prefer), pass it directly:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
YAY_PASSPHRASE="your-passphrase" yay keygen --name you
|
|
125
|
+
# or: yay keygen --name you --passphrase "your-passphrase"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Each `yay keygen` adds you to the **roster** in `.yaylayer/config.json`. A name can be a simple handle **or** a full name — just quote names with spaces (`--name "Alice Carlsen"`). Two examples of how they land in the JSON:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"project": "My App",
|
|
133
|
+
"signers": {
|
|
134
|
+
"alice": "MCowBQYDK2VwAyEAyQptzSJTyB2Nh+MKQA6SVhiZ74M5Kk5qM83O2MGU9Q=",
|
|
135
|
+
"Alice Carlsen": "MCowBQYDK2VwAyEA0f3b9c1d2e4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a1="
|
|
136
|
+
},
|
|
137
|
+
"owners": ["alice", "Alice Carlsen"]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The name you pick is what shows up as the signer on every seal (`signed by alice` / `signed by Alice Carlsen`) — so it's who gets the credit/blame for each approval. `signers` holds public keys only; private keys stay in `.yaylayer/keys/` (gitignored).
|
|
142
|
+
|
|
143
|
+
> **`yay init` is once per project — tool upgrades just work.** The `.yaylayer/` data (roster + seals) is forward-compatible, so when `yay` itself is updated your projects pick up the new behaviour **automatically — you never re-init**, and existing signatures stay valid. For example, the AST-coverage upgrade instantly makes previously-invisible code show up **Pink** on an already-initialized project; just run `yay verify` again. You only `init` a brand-new project.
|
|
144
|
+
|
|
145
|
+
## A Cell looks like this
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
//∷YAY⟨C-040⟩ v1
|
|
149
|
+
// unit: calcPortfolioValue
|
|
150
|
+
// lang: js
|
|
151
|
+
// intent: Sum each holding's quantity times its price into one USD total.
|
|
152
|
+
// in: holdings: Array<{qty:number, priceUsd:number}>
|
|
153
|
+
// out: totalUsd: number
|
|
154
|
+
// pure: yes
|
|
155
|
+
// feeds: C-041
|
|
156
|
+
//∷YAY-END⟨C-040⟩
|
|
157
|
+
function calcPortfolioValue(holdings) {
|
|
158
|
+
return holdings.reduce((t, h) => t + h.qty * h.priceUsd, 0);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Machine fields** (fill precisely, plus one plain `intent:` sentence): `unit · lang · in · out · pure · ensures · throws · effects · feeds`. A few unlock deeper checking: **`ensures`** (a boolean expression → machine-**proven** Green + mutation grading + inertness), **`renders: yes`** (a React/JSX component → proven against its rendered tree with `text/find/attr/hasClass`), **`records: <param>`** (an effectful unit → asserted against a recorded call/set trace), and two *declarations* that keep a Cell honest under the inertness check — **`throws: <condition>`** (a declared guard) and **`perf: <reason>`** (intentional semantically-invisible code like a cache). A governance hint, **`risk: low|medium|high`** (default `medium`), feeds Autopilot's `--max-risk` ceiling — high for money/auth/secrets/deploy/CI/irreversible, low for cosmetic or pure helpers. It's declared, not proven, and never overrides the security guard or owner-signed policy.
|
|
163
|
+
|
|
164
|
+
## What a signature attests to
|
|
165
|
+
|
|
166
|
+
**A signature covers the *specification* — the spec block's hash plus the Brief — and *never the implementation*.** The signed bytes are `canonical(approval)`, whose `items` map is `{ Cell-id → sha256(normalized spec block) }`, together with the Brief (title, prose, tags). **No code hash is ever part of the signed object.**
|
|
167
|
+
|
|
168
|
+
That single choice *is* the architecture — call it **A**: the human attests only to the spec, and the verifier continually re-establishes implementation → specification.
|
|
169
|
+
|
|
170
|
+
- **`yay verify` re-derives code → spec on every run** from the real files — static checks (the unit exists, declared `pure`/`effects` hold, no undeclared effects — per language) plus a **behavioural prover** with **mutation grading** and an **inertness check** for JS/TS, a **render prover** for React/JSX components (`renders: yes` → checked against the rendered tree), and an out-of-process **prover for pure Python functions**. This link is **never signed; it is re-checked, always,** against whatever the code currently is.
|
|
171
|
+
- **Edit the code, not the spec:** the seal still verifies (it's over the spec), and `verify` decides the colour — a faithful refactor stays **Green**, a drift from the promise flips **Red**. You refactor freely, no re-approval needed.
|
|
172
|
+
- **Edit the spec or the Brief:** the hash changes, the seal no longer matches, and the Cell drops to **Unsigned** — the promise itself changed, so it needs a fresh human signature.
|
|
173
|
+
|
|
174
|
+
So a **Green** Cell asserts two independent facts at once: *a human signed this exact promise* (the seal — cryptographic, offline-verifiable) **and** *the code provably keeps that promise right now* (verify — continuously re-derived). It is emphatically **not** option B (an implementation hash baked into the seal); the seal deliberately says nothing about the code, which is what lets refactors stay Green while genuine drift goes Red. Tampering with either half is caught: the lock is an append-only `prev`-chain, and verify never trusts a stored say-so — it recomputes from source every time.
|
|
175
|
+
|
|
176
|
+
## Signing that fits your pace — batching
|
|
177
|
+
|
|
178
|
+
The failure mode for any approval tool is fatigue: *"pull out the phone → approve → repeat"* until people rubber-stamp. YayLayer avoids it two ways.
|
|
179
|
+
|
|
180
|
+
**You sign intent, not edits.** A refactor or tweak that keeps the same spec needs **no new signature** (see above) — so a lot of small work never touches the phone at all.
|
|
181
|
+
|
|
182
|
+
**Batch mode (on by default) groups the small stuff.** The typical flow:
|
|
183
|
+
|
|
184
|
+
- Ask for a **big change** → the AI writes its specs + a Brief and you **sign it right away** (one Brief, one signature).
|
|
185
|
+
- Ask for a **small change** → the AI still writes its spec + code immediately (so nothing is untracked and you can **try it in action** right away, Unsigned), and quietly adds it to a **pending batch** — grouped **per concern** (by tag). When a batch reaches the **barrier** (default **5**) — or you're about to commit/push — it shows you the batch with a proposed title and asks: **close & sign, add one more, or keep going?** So a run of ten small tweaks becomes **one** signature, at a boundary you choose.
|
|
186
|
+
- Anything **sensitive, behaviour-changing, or policy-required** is never batched — it gets its own Brief immediately.
|
|
187
|
+
- Under **Autopilot**, batches form the same way but each is approved under your grant (delegated, awaiting ratification) (one Brief per batch, never per tiny change).
|
|
188
|
+
|
|
189
|
+
Tune it with **`yay batch <n>`** (raise/lower the barrier), `yay batch off` (a Brief per change), or the batch control in the dashboard's Briefs tab. Committing/pushing always flushes pending batches, so nothing rots unsigned.
|
|
190
|
+
|
|
191
|
+
## Commands
|
|
192
|
+
|
|
193
|
+
| Command | What it does |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `yay init [dir] [--project <name>]` | guided setup — signing method, adopt, Brief tags, Constitution, optional plan |
|
|
196
|
+
| `yay keygen --name <you>` | create a local signing key (public → roster, private → encrypted keystore) |
|
|
197
|
+
| `yay pair [--name you]` | pair your **phone** as the signer (key stays on the phone); first pairing roots trust |
|
|
198
|
+
| `yay adopt [path] [--dry]` | insert draft (unsigned) spec blocks above un-tagged units |
|
|
199
|
+
| `yay sign [--cell IDs] [--brief "…"] [--title "…"] [--tags "…"]` | ask for approval — routes to your phone; `--name "<other>"` routes to a teammate's inbox, `--check` collects it |
|
|
200
|
+
| `yay verify [--strict] [-d]` | the gate — paint every Cell + run the prover; `--strict` exits non-zero if blocked (CI) |
|
|
201
|
+
| `yay dashboard [--port N]` | live control panel + phone relay — map, Preview (run scripts), tests, sign requests, **Ask** |
|
|
202
|
+
| `yay ask "<question>"` | ask the configured AI about THIS repo + the manual (primed with live state; needs an LLM key in `.env`) |
|
|
203
|
+
| `yay briefs [--by-tag] [--tag X]` | the Brief ledger (newest-first, or grouped/filtered by tag) |
|
|
204
|
+
| `yay tags [--set id\|add\|remove\|rename\|sets]` | the project's Brief-tag vocabulary (six sets or custom) |
|
|
205
|
+
| `yay inbox` / `yay requests` | your on-duty relay link · plain requests queued from the dashboard |
|
|
206
|
+
| `yay invite "Name"` · `yay enroll` · `yay revoke` · `yay reroot` | team roster — one-tap join, enroll/revoke a key, re-root trust |
|
|
207
|
+
| `yay policy [--init\|--set]` · `yay grant [--allow\|--deny\|--allow-tag\|--deny-tag\|--max-risk\|--child-grants\|--no-guard]` · `yay ratify [--sign\|--reject]` | signing policy (who must sign) · Autopilot capability-envelope grants (auth/payments/secrets/deploy/CI **blocked by default** — lift per-grant with `--no-guard`; + child grants for helper-agent swarms) · ratify or reject delegated approvals |
|
|
208
|
+
| `yay attest [list\|verify]` · `yay reverify [--all\|posture]` · `yay witness` · `yay metrics` · `yay capability` | the machine verifier signs its own verdict (chained, capability-versioned) · re-verify history: `--all` sweeps it into a keyless upgrade report, `--attest` appends signed records, `posture` sets grandfathering — never rewriting old Green · integrity witness · earned-autonomy metrics · the verifier's derived capability + drift check |
|
|
209
|
+
| `yay archive [install\|--restore\|--verify\|--forget]` | Durable mode — encrypted, sha256-anchored archive of signed source (secret scan + signed tombstones) |
|
|
210
|
+
| `yay protect [--mode guarded\|strict] [--add\|--remove\|--ignore] [--off]` | owner-signed **foundation seal** — reveal any change to the fixed core files (rules, CI, gitignore) |
|
|
211
|
+
| `yay test` · `yay adversary` · `yay plan` | run the project's own test suite · spec-only adversarial probing (LLM sees only the spec) · AI-synthesized System Plan |
|
|
212
|
+
| `yay map [-o file.html]` · `yay gate` · `yay constitution --for <keys>` · `yay status` | write the HTML map · write the CI gate · write the Constitution into your AI harness · one-line summary |
|
|
213
|
+
| `yay id` · `yay merge` | this clone's Cell-id shard + next id (ids are `C-<shard>-n`, per working copy, so they never collide on merge) · post-merge health check: re-verify + list any collisions or Cells needing re-signing |
|
|
214
|
+
|
|
215
|
+
## Colors
|
|
216
|
+
|
|
217
|
+
🟢 **GREEN** code proven to match a signed spec · 🟡 **YELLOW** matches but flagged (prose-only, undeclared effect, unproven) · 🔴 **RED** code ≠ spec (or tampered signature) · ⚪ **UNSIGNED** awaiting a signature · 🩷 **PINK** code with **no formal specification at all** — untracked, never described or signed.
|
|
218
|
+
|
|
219
|
+
**Total coverage is the whole point.** PINK is the most dangerous state — unknown territory where silent bugs hide — so it **blocks the gate just like Red and Unsigned.** YayLayer never silently ignores code it doesn't understand: **any named unit** with no spec block — a function, object method, class method, or arrow-prop, *even nested inside an IIFE, object, or class* — shows up **Pink** (`«unitName»`) until you `yay adopt` it and sign it. Top-level imperative code that runs at load is flagged too. That way "green gate" honestly means *the whole project is covered*, not just the parts someone happened to tag. (The signature covers the **spec**, so you can still refactor freely; only a changed promise re-prompts you. Coverage uses a real parser — [`@babel/parser`](https://babeljs.io/docs/babel-parser) — so **JS, TypeScript, JSX and TSX** are all handled; genuinely unparseable files degrade gracefully to file-level grouping.)
|
|
220
|
+
|
|
221
|
+
### What happens if an AI injects code that wasn't there before?
|
|
222
|
+
|
|
223
|
+
In the common cases it lands in a **gate-blocking state**, and the AI has no key to turn any of them Green. Three ways it surfaces:
|
|
224
|
+
|
|
225
|
+
1. **A new function or unit with no spec → 🩷 Pink.** `verify` enumerates *every* named unit; anything untracked is Pink, and Pink blocks the gate like Red. This is where an exfiltration payload usually lives — a new helper or a top-level call.
|
|
226
|
+
2. **Behaviour that needs a new or changed promise → ⚪ Unsigned.** Editing a spec makes its Cell Unsigned — and the AI can't sign it back.
|
|
227
|
+
3. **Lines added to an existing signed function whose behaviour now contradicts its spec → 🔴 Red.** `verify` re-derives code⇔spec on every run: an undeclared side effect (network, filesystem, `localStorage`) in a `pure` Cell, or an output that breaks the `ensures`, turns it Red — and the mutation grader and spec-only adversary hunt for behaviour the `ensures` doesn't pin down.
|
|
228
|
+
|
|
229
|
+
**The honest boundary:** a payload that is *pure*, fully consistent with the signed `ensures`, and dormant until a trigger the generated inputs never hit could stay Green — `verify` proves the spec's *claims*, not the absence of all hidden behaviour. Effect-recording, mutation testing, the three-prong pincer (inertness · literal-seeding · undeclared-input predicate provenance), branch-exercise honesty, the adversary, and reviewing the diff raise that bar without eliminating it. The guarantee is strongest on **JS/TS/JSX** (full parser + the Pink net + adversary). Note the signal is the **state** (Pink/Unsigned/Red), not a line-level diff — there's no code snapshot in the seal, by design (the signature attests to the *spec*).
|
|
230
|
+
|
|
231
|
+
## Higher-order: modules, flow & policies
|
|
232
|
+
|
|
233
|
+
YayLayer isn't only per-Cell — it models how Cells combine.
|
|
234
|
+
|
|
235
|
+
**Modules (`contains`).** A "module" is simply a Cell that declares `contains` instead of governing its own code. Its color **rolls up** to the worst of everything inside it, so a deep Red bubbles to the top and you can trace it down. By convention, container Cells use a high id range (`C-900+`):
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
//∷YAY⟨C-900⟩ v1
|
|
239
|
+
// unit: Portfolio
|
|
240
|
+
// intent: The portfolio feature, composed of its calc Cells.
|
|
241
|
+
// contains: C-040, C-041
|
|
242
|
+
//∷YAY-END⟨C-900⟩
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
If a contained Cell is Red, `C-900` **rolls up to Red** — *"rolled up from contained Cells"* — so a deep failure bubbles to the top and you can trace it down. In the map it's tagged `· module`, and its popup lists what it **Contains**.
|
|
246
|
+
|
|
247
|
+
**Flow (`feeds`).** A Cell lists the Cells it hands output to with `feeds`. That builds the graph the map draws, and verify flags a **broken edge** — a `feeds →` pointing at a Cell that doesn't exist.
|
|
248
|
+
|
|
249
|
+
**Policies (`P-…`) — roadmap.** Cross-cutting concerns that span many Cells and live in no single one (auth, logging, error handling) are modeled as first-class **Policies**: a selector (which Cells) + a checkable rule, verified as a **"for all matched Cells"** check — e.g. *"every route is behind auth."* Flavors: mandate / prohibit / grant. Designed in [`standard/STANDARD.md`](standard/STANDARD.md) §7 and `docs/`; not yet in the reference implementation.
|
|
250
|
+
|
|
251
|
+
**Implemented today:** `contains` roll-up, the `feeds` graph, and broken-edge detection — plus a **signing policy** (a different, shipped thing: owner-signed "who must sign what" rules, matched by path / tag / module, enforced at the gate — see the Standard §11). Full flow-contract checking (`producer.out ⊨ consumer.in`) and the richer checkable **Policy** engine (mandate / prohibit / grant) are on the roadmap.
|
|
252
|
+
|
|
253
|
+
## CI gate
|
|
254
|
+
|
|
255
|
+
`main` should be protected with a required check running:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
yay verify --strict
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Anything Red or Unsigned fails the check, so it can't be merged. The real enforcement lives here, in infrastructure the AI doesn't control — not in a local hook.
|
|
262
|
+
|
|
263
|
+
**Any git host — not just GitHub.** `yay gate --for <platform>` writes the right pipeline and prints that host's branch-protection steps:
|
|
264
|
+
|
|
265
|
+
| `yay gate --for …` | writes | protect main via |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| `github` *(default)* | `.github/workflows/yaylayer.yml` | Rulesets → require the `gate` check + PR |
|
|
268
|
+
| `azure` | `azure-pipelines.yml` | Branch policies → **Build Validation** |
|
|
269
|
+
| `gitlab` | `.gitlab-ci.yml` | Protected branch + **"Pipelines must succeed"** |
|
|
270
|
+
| `bitbucket` | `bitbucket-pipelines.yml` | Branch restrictions → require a passing build |
|
|
271
|
+
| `gitea` | `.gitea/workflows/yaylayer.yml` | Branch protection → require the `gate` check |
|
|
272
|
+
| `gerrit` | `.zuul.yaml` + `playbooks/yaylayer-gate.yaml` | Gates via the **Verified** label — make Verified+1 a **submit requirement** (CI: Zuul, or Jenkins via the Gerrit Trigger plugin) |
|
|
273
|
+
|
|
274
|
+
The contract is identical everywhere — run `yay verify --strict` on a protected branch, root pinned; only the syntax differs. The core is pure git, so it works on any host or none.
|
|
275
|
+
|
|
276
|
+
## Briefs & Git — two ledgers that move together
|
|
277
|
+
|
|
278
|
+
A **git commit** snapshots *code*; a **Brief** is the human-signed record of *intent* (you sign the **spec + Brief, never the code**). They live in different places a single commit unites: the **spec Cells live inline in your source** (so they commit with the code), while the **Brief and its seal live in `.yaylayer/`** (also git-tracked). `yay verify` never trusts a stored verdict — it **re-derives** each Cell from the tree, and the CI gate runs that on the **pushed commit**. So the seal and the code it authorizes must ride the **same commit**: commit code without its seal → that commit is **Unsigned** (blocked); edit a signed Cell's spec afterwards → its `specHash` changes and the seal no longer matches (back to needs-signing).
|
|
279
|
+
|
|
280
|
+
**Your AI is instructed to keep them in lockstep.** The Constitution written into your AI-harness files (`yay init --constitution`, Article 4) tells it: *when `yay verify` passes, **commit the code and `.yaylayer/` together in ONE commit**, and do **not** `git push` unless the human asks.* So each signed Brief lands as its own self-contained, human-authorized commit — pushing stays your call. It isn't strictly one-Brief-per-commit (batch mode groups small changes into one Brief), but the invariant holds: **never a commit with authorized code but no seal, or a seal with no code.** git records *what the code is*; the Brief records *that a human approved what it should be* — committed together so `yay verify` can prove the two still agree.
|
|
281
|
+
|
|
282
|
+
## What's built vs planned
|
|
283
|
+
|
|
284
|
+
A **working reference implementation** of the protocol — honest about scope.
|
|
285
|
+
|
|
286
|
+
**Verification & the gate:** marker extraction · manifest + `sha256` spec hashing · **ed25519** sign & verify over an append-only, `prev`-chained lock · the green/yellow/red/unsigned/**pink** gate with static, **per-language** code⇔spec checks (unit exists, declared purity holds, undeclared-effect flags with the offending line pinpointed — JS *and* Python effect nets) · a **pluggable prover** behind one adapter interface: a **behavioural prover + mutation grading + inertness check** for JS/TS, a **render prover** for **React/JSX** components (`renders: yes` → checked against the rendered tree, so components reach machine-**proven** Green), and an out-of-process **Python prover** so **pure Python functions with an `ensures` also reach proven Green** · **AST coverage** (`@babel/parser` — JS/TS/JSX/TSX) plus a **comment-agnostic + regex net for Python/Ruby** so every un-specced unit — *and top-level imperative code, in every scanned language* — shows **Pink** and blocks the gate · the remaining languages (C#, Rust, Go, Solidity, …) parse comment-agnostically and are capped honestly at **Yellow** · **`adopt`** · the interactive map (hierarchy, zoom, roll-up, module-flow, plus **Briefs / Tags / Policy / Signers** tabs) · guided `yay init` · `--strict` CI gate.
|
|
287
|
+
|
|
288
|
+
**Built-in security features:** a three-prong **anti-payload pincer**, all deterministic — (1) the **inertness check** flags code removable with every spec-derived test still passing (dead weight, ahead-of-spec scaffolding, or a **dormant payload** riding under a signature); (2) **literal-seeding** harvests the constants a branch compares against, builds the exact input that fires a hidden trigger, and turns a contradicting payload **Red** with a concrete reproduction; (3) **undeclared-input predicate provenance** flags a branch that keys off a parameter the Cell's `in:` never declares (`if (mode === 'admin')` when `in:` lists only `items`) — the hidden-mode shape the first two structurally miss. Each is **Yellow by default** and escalatable to gate-blocking with an **owner-signed** policy rule (`inert: block` · `predicate: declared`), or relaxable to a note. Plus **branch-exercise honesty** — a proven Cell shows how many branches its spec-derived inputs actually exercised (*"proven — 5/7 branches"*), an honest badge that never fakes Green, with `coverage: full` to demand 100% for crown-jewel scopes · **tamper-evident `.yaylayerignore`** — hiding *source* from the gate is Pink-blocking unless whitelisted by an owner-signed `ignore: source` policy rule (so the AI can't hide code by ignoring it).
|
|
289
|
+
|
|
290
|
+
**Phone signing is built** (not a stand-in): pair your phone over the **LAN** or via the end-to-end-encrypted **`relay.yaylayer.com`** — the key is generated on the phone and never touches the AI's machine · **24-word mnemonic** recovery · a live **dashboard** (`yay dashboard` — run tests, run `package.json` scripts, request changes, sign) · **teams** — a signed, hash-chained roster with owner/signer roles and `yay invite` / `enroll` / `revoke` / `reroot` · **signer routing** (`yay sign --name` → a teammate's inbox; fire-and-return) · **Autopilot** grants + ratification (`yay grant` / `yay ratify`) · a **signing policy** (who-must-sign, owner-signed into the roster) · **Briefs** with a short title + the prose, browsable as a list or a **Cloud view** (a card per tag) and in the terminal (`yay briefs`) · **Brief tags** (six starter sets or custom; the signer can correct the AI's tags on the phone at signing).
|
|
291
|
+
|
|
292
|
+
**Provenance & assurance is built** — the third cryptographic identity and the long-lived audit layer: **verifier attestation** (`yay attest` — the machine verifier signs its *own* verdict with a project/CI-scoped key that never ships in the package; append-only, chained, **capability-versioned**, and the version is **derived + self-asserting** so an attestation can never over-claim — `yay capability`) · **`yay reverify --all`** replays your whole preserved history through today's verifier into a keyless **upgrade report**, `--attest` records each re-assessment as a signed event beside the old (never rewriting Green), and a **grandfathering posture** (off/guarded/strict, scopeable) governs whether a capability bump warns or blocks · **`yay witness`** cross-checks the attestation chain, spec archive, and git-vs-ledger coverage · **`yay metrics`** turns your rejection history into earned-autonomy signals · **Autopilot** grants are signed **capability envelopes** (allow/deny paths, cells, `--max-risk`, and opt-in **child grants** for helper-agent swarms that can only attenuate) with **non-delegable** owner-signed backstops and first-class **rejections** · **Durable mode** (`yay archive`) — an encrypted (AES-256-GCM), sha256-anchored archive of the signed source with a pre-archive **secret scan** and honest signed **tombstones** · and a **keyless verifier + capability registry** so anyone can confirm an attestation in-browser at [yaylayer.com/verify](https://yaylayer.com/verify).
|
|
293
|
+
|
|
294
|
+
**Roadmap:** more prover **adapters** so the remaining signed-only languages (C#, Rust, Go, Solidity, …) also earn machine-**proven** Green — the interface is built (React and Python are the first two adapters); mutation grading + inertness for the out-of-VM (Python) adapter · **M-of-N multi-sig** · richer **Policy** flavors (mandate / prohibit / grant as checkable "for all matched Cells" rules) · scope-aware flow-contract checking · automated merge re-proving · LLM-driven `adopt` intent derivation.
|
|
295
|
+
|
|
296
|
+
## Mobile signing — the safety model
|
|
297
|
+
|
|
298
|
+
Every color in YayLayer ultimately rests on one thing: a **human signature** over the spec. That makes the signing key the crown jewel — whoever holds it can approve code *as you*. If that key ever sat on the machine the AI runs on, the AI (or any malware there) could forge your approval and paint its own code Green. **Mobile signing removes the key from the AI's reach entirely** — and it's built: pick it at `yay init` (Mobile-LAN or Mobile-relay), then `yay pair` your phone. *(A local passphrase-encrypted keystore is still available for solo work and CI, chosen with the Local option.)*
|
|
299
|
+
|
|
300
|
+
**Where the key lives.** Your private key is generated on your **phone** and never leaves it — held in the phone's secure hardware (Secure Enclave / Android Keystore) and released only by **Face ID / biometric**, per signature. The AI's machine only ever sees your **public** key (in the committed roster).
|
|
301
|
+
|
|
302
|
+
**How you approve — the flow:**
|
|
303
|
+
|
|
304
|
+
1. **Pair once.** Scan a QR code to enroll your phone's public key into the project roster. After that the phone and `yay` talk over an encrypted push channel.
|
|
305
|
+
2. **Request.** When specs are ready, `yay` sends an **approval request** — the spec **hashes**, the plain-English `intent` of each Cell, and a per-request **nonce** — up to a **rendezvous server**.
|
|
306
|
+
3. **Review on-device — and the phone *proves* it (WYSIWYS).** Your phone shows *exactly what you're signing*, and it doesn't just trust what the laptop sent: before you can Accept, it **recomputes each spec's `sha256` on the phone itself** and **refuses to sign unless that hash equals what the signature would bind** — the review is rendered *from* those verified bytes. On any mismatch the Accept button is hidden and you're warned. So a compromised laptop or hijacked AI **cannot show you one spec and have you sign another.** This covers **every kind of phone signing** — code approvals *and* governance events (grants, the foundation seal, enroll/revoke/reroot/policy), each rendered from the exact event the key signs.
|
|
307
|
+
4. **Sign.** You confirm with Face ID; the phone signs the canonical bytes and returns only the **signature**; `yay` appends it to `.yaylayer/lock.json`.
|
|
308
|
+
|
|
309
|
+
**Why it's safe — what each party can and can't do:**
|
|
310
|
+
|
|
311
|
+
- **The rendezvous server (and the laptop) can't trick you.** The server relays ciphertext only — it never sees your code and never holds your key. With no key it **cannot forge** a seal, and because the **phone itself re-derives and re-checks the spec hashes on-device** (WYSIWYS, above) and the signature covers them, neither the server *nor a compromised laptop* **can alter what you approved** — a mismatch is refused, not signed. The worst either can do is drop or delay a request (annoying, not dangerous).
|
|
312
|
+
- **The AI can't self-approve.** It never touches the private key, so it can produce specs and code but **never a valid signature** — its work stays Unsigned until *you* sign.
|
|
313
|
+
- **Replays are dead on arrival.** The per-request **nonce** means a captured approval can't be re-submitted to bless different code.
|
|
314
|
+
- **Tampering is caught.** The lock is an append-only **`prev`-chain** (tamper-evident history), and `verify` always **recomputes hashes from the real files** — so editing code after it was signed flips it Red even though the old seal still verifies.
|
|
315
|
+
|
|
316
|
+
So *"can someone with a private key fake-sign?"* — only the holder of **your phone plus your face** can, which is the whole point: a green gate provably means **you** stood behind it.
|
|
317
|
+
|
|
318
|
+
**If you lose the phone.** Your key backs up as a **24-word mnemonic + passphrase** — so a lost phone **restores the *same* key** with nothing to re-sign. Pick the right path by *why* you lost access: **have your 24 words** → restore (no discontinuity); **lost the words, solo** → `yay reroot` (new trust root); **lost the words / compromised, on a team** → another owner **revokes** the old key and **enrolls** a new one (root untouched). For a *compromised* key, restore is useless — the attacker has the same key — so you **retire** it (reroot) or **revoke** it, not restore. `yay reroot` is the deliberate last resort.
|
|
319
|
+
|
|
320
|
+
**Is `yay reroot` a backdoor?** No — it only *proposes* a new root; the **CI root-pin blesses it**. A reroot changes the root fingerprint, so `yay verify` reports **TRUST-ROOT MISMATCH** and the **gate blocks** — a loud alarm, not a silent takeover. Making a new root real means a human repoints the pin via `yay gate` in GitHub's **branch-protected settings** (outside the repo and the AI's reach). So repo-write alone can *trip the alarm* but can't take over without also compromising your GitHub settings, and reroot can't rewrite past signatures. The one caveat: with no CI gate/pin (local-only) a reroot has no backstop — but then nothing is truly enforced anyway.
|
|
321
|
+
|
|
322
|
+
## Who runs `yay sign`: the AI asks, you approve
|
|
323
|
+
|
|
324
|
+
Signing has **two roles**, and keeping them straight avoids a lot of confusion:
|
|
325
|
+
|
|
326
|
+
- **The AI runs `yay sign`** — this is *asking for approval*. The command builds the change-set and sends the request to your phone; it then **blocks until you answer**.
|
|
327
|
+
- **You approve on the phone** — this is *giving the signature*. Your phone is the only place the key exists; you review the Brief + specs and tap **Accept** (or **Send back**).
|
|
328
|
+
|
|
329
|
+
So **let your AI run `yay sign` as its own tool call** and wait for your tap. If *you* run `yay sign` yourself in a separate terminal, the AI that's building for you didn't launch it — it can't see the result and won't automatically continue once you've approved. (Running it yourself is fine when *you're* the one driving; just don't do it in parallel with an AI that's waiting on its own request.) The phone shows the requested signer's name and blocks the wrong person from approving; on a team, use `yay sign --name "<Teammate>"` to route the request to *their* inbox instead (see **signer routing** in the Standard).
|
|
330
|
+
|
|
331
|
+
## HTTPS & trusting the certificate on your phone
|
|
332
|
+
|
|
333
|
+
Phone signing is served over **HTTPS by default** (`--no-https` opts out). This is about *server identity*, **not** signature security: your ed25519 signatures are safe over any transport — the seal covers the spec hash and is verified offline — so a certificate warning never weakens what you're signing. HTTPS just stops a same-network attacker from impersonating the signing page.
|
|
334
|
+
|
|
335
|
+
A self-signed cert works but the phone will warn once. To make it **warning-free**:
|
|
336
|
+
|
|
337
|
+
**1. Trust the CA on your laptop (mkcert).**
|
|
338
|
+
```bash
|
|
339
|
+
brew install mkcert && mkcert -install
|
|
340
|
+
```
|
|
341
|
+
Chrome/Safari read the macOS **system keychain**; **Firefox** keeps its own store, so also `brew install nss`. **After installing mkcert you must restart `yay dashboard`** — a running server keeps serving the *old* self-signed cert until restarted. Chrome caches its "not secure" verdict, so fully quit (⌘Q) and reopen. (Safari shows no padlock detail for local certs — "no warning" *is* the pass.)
|
|
342
|
+
|
|
343
|
+
**2. Get the CA onto the phone.** The root is at `~/Library/Application Support/mkcert/rootCA.pem` — copy it to your Desktop and **AirDrop** it to the phone (or open `<dashboard-url>/trust` on the phone for a guided flow).
|
|
344
|
+
|
|
345
|
+
**3a. iOS — the two-screen gotcha.** Installing the profile is only step 1: **Settings → General → VPN & Device Management** → install the profile (there's *no* trust toggle here). The trust toggle is somewhere else: **Settings → General → About → (scroll to the very bottom) → Certificate Trust Settings → Enable Full Trust For Root Certificates → toggle the mkcert entry ON.** If that row is missing, the file was opened as a preview rather than installed — re-transfer it.
|
|
346
|
+
|
|
347
|
+
**3b. Android.** **Settings → Security (or Security & privacy) → Encryption & credentials / More security → Install a certificate → CA certificate** → pick the file → accept the warning. (Android 7+ apps don't trust user-added CAs by default, but **browser** traffic does, so the signing page works. Menu names vary by manufacturer.)
|
|
348
|
+
|
|
349
|
+
**Note:** the cert is bound to your machine's **LAN IP** (in the SAN), so it regenerates when that IP changes; the phone key is per-origin, so a new IP just means restoring the key from your 24 words. A future hosted domain with a real CA cert removes this whole dance.
|
|
350
|
+
|
|
351
|
+
## Foundation seal — tamper & corruption evidence for the core
|
|
352
|
+
|
|
353
|
+
Ordinary code changes freely — that's the job, and it's governed by the normal spec→sign→verify gate. But a few files define **how the AI and the rules operate** and should stay fixed in ~99% of cases: the **Constitution** (`CLAUDE.md`/`AGENTS.md`…), the **CI gate workflow**, `.gitignore`/`.yaylayerignore`, the protocol files. The **foundation seal** is an **owner-signed baseline** of that fixed core, so any change to it is **revealed** at `yay verify` — whether from a hijacked AI, disk/bit-rot corruption, a bad merge, or an accident. It's **detection, not prevention**, and **cause-agnostic**: the current bytes simply no longer match what the Owner last vouched for. The baseline is signed by the **Owner's phone key** (never on the AI's machine), so it's math a rogue agent can't forge — you're never left blind to a change in the crown jewels.
|
|
354
|
+
|
|
355
|
+
It watches **content drift** (the hash of each sealed file — for rule files, only the managed `YAYLAYER:BEGIN…END` block, so your own notes around it stay free) and **structural drift** (a new or removed *tracked* file in a watched zone — repo root, `.github/`, `.yaylayer/`). That complements the Pink net: **Pink catches rogue *code*; the seal catches rogue *files*** of any type (a workflow, a shell script). `.gitignore` is itself sealed, because it controls the blind spot.
|
|
356
|
+
|
|
357
|
+
- **Postures, picked at `yay init`/`adopt` (default Guarded):** **Off** (no seal) · **Guarded** (drift raises a loud warning, gate still passes) · **Strict** (drift blocks the gate until you re-seal). The only difference between Guarded and Strict is warn-vs-block; nothing else changes. *Secure by default, yours to tune.*
|
|
358
|
+
- **Un-removable:** the seal is an owner-signed, append-only, trust-root-pinned event — deleting or disabling it breaks the pinned chain and is itself flagged. Only a human Owner can create, re-seal, or turn it off.
|
|
359
|
+
- **Signed ignore list:** a tracked file that legitimately churns (a committed CHANGELOG) can be excluded — but the exclusion lives *inside the owner-signed seal*, so the AI can't add `ignore: payload.js` without the phone key. (Gitignored files like `.env` are already outside the watch.)
|
|
360
|
+
- **Re-seal** on a legitimate change with `yay protect` (one phone tap) — the AI can't clear the alarm; only a human with the Owner key can. Each seal is an append-only signed event, so the foundation has its own audit trail.
|
|
361
|
+
|
|
362
|
+
## Security notes
|
|
363
|
+
|
|
364
|
+
- **Never commit private keys or secrets.** `.yaylayer/keys/`, `*.keystore`, and `.env*` are gitignored. `config.json` (public keys) and `lock.json` (seals) *are* committed — that's the shared proof state.
|
|
365
|
+
- **Phone signing** keeps the private key **only on your phone**, so it never touches the AI's machine — choose Mobile-LAN or Mobile-relay at `yay init`. The **Local** option (a passphrase-encrypted keystore on disk) is available for solo work and CI; treat that keystore as sensitive since the key lives on the machine.
|
|
366
|
+
- **The verifier isn't in your repo.** `yay verify` (the code that assigns Green/Yellow/Red) is the installed `yay-layer` package; CI installs it **fresh** on a clean checkout and pins the trust root — so the AI can't edit the colour rules to reach the gate, and self-enrolling in `config.json` is rejected (trust comes only from the owner-signed roster).
|
|
367
|
+
- **You can't hide code from the gate.** A `.yaylayerignore` line that excludes *source* turns the gate **Pink** unless an owner-signed policy `ignore: source` rule authorises it (for vendored/generated code). Build artifacts and non-code stay free to ignore.
|
|
368
|
+
- Verification always **recomputes hashes from the real files** and checks signatures against public keys — nothing is trusted on a stored say-so.
|
|
369
|
+
|
|
370
|
+
## Contributing
|
|
371
|
+
|
|
372
|
+
YayLayer is early, MIT-licensed, and built in the open — a great time to help shape the protocol. High-leverage areas:
|
|
373
|
+
|
|
374
|
+
- **Language adapters** — per-language AST + behavioural proving so the *signed-only* tier (Python, C#, Rust, Go, Solidity, …) can also earn machine-**proven** Green.
|
|
375
|
+
- **Teams & policy** — M-of-N multi-sig for crown-jewel Cells, safe merge re-proving, and the richer Policy engine (mandate / prohibit / grant).
|
|
376
|
+
- **Real-world use** — try it on a project and open issues: rough edges, confusing docs, missing comment syntaxes. Field feedback shapes the roadmap most.
|
|
377
|
+
- **Docs & examples** — walkthroughs, example projects, integrations.
|
|
378
|
+
|
|
379
|
+
How to start: read [`CONSTITUTION.md`](CONSTITUTION.md) and [`standard/STANDARD.md`](standard/STANDARD.md) (the protocol's source of truth), run `node test/smoke.js` before a PR, and **open an issue to discuss anything non-trivial first**. Keep changes spec-first — YayLayer is built with YayLayer, so `yay verify` should stay green. Issues and PRs: [github.com/jonas-developer/yay-layer](https://github.com/jonas-developer/yay-layer).
|
|
380
|
+
|
|
381
|
+
## License
|
|
382
|
+
|
|
383
|
+
MIT © L.J Bergman. See [`LICENSE`](LICENSE).
|