Repository navigation
chore(deps): bump the airlock-npm group across 1 directory with 14 updates - #31
Closed
dependabot[bot] wants to merge 123 commits into
Closed
dependabot[bot] wants to merge 123 commits into
dependabot[bot] wants to merge 123 commits into
Conversation
npm workspaces, MIT licence, and the dependency overrides that make @truefoundry/trueforge-ui@0.2.4 build at all. @assistant-ui/core peer-depends on zustand@^5 while the OpenUI renderers pull zustand@^4, which npm hoists to the root. The build then fails with "'useShallow' is not exported from 'zustand/shallow'" and npm ls reports the tree as invalid. Pinning zustand to ^5 resolves it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Everything checked against trueforge.dev and the published packages on 24 August 2026. TrueForge shipped on 19 August, so nothing about its API can be recalled from memory. Records three corrections that change the build (section 4): - Subagents are dynamic. The root agent spawns them at runtime via create_sub_agent; there is no per-subagent block in the agent spec, so "four named subagents" is not implementable as planned. - Per-subagent tool scoping does not exist. The docs are explicit that subagents share the root agent's tools and sandbox. Least privilege has to be enforced at the agent boundary instead. - Per-subagent model routing does not exist either. Routing is real at the agent boundary; the model and cost per lane are readable from thread.created.agentInfo.model and turn.done.state.metrics. Also records that trueforge@0.1.4 does not start on Windows (ESM loader, "Received protocol 'c:'") and that its local sandbox is macOS/Linux only, so Windows needs Docker hosted mode. Section 10 lists what could NOT be verified, so nothing downstream gets built on a guessed API surface. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The one contract everything hangs off: the agent produces a dossier, the verifier fills in its certificate, the console renders it, the gate reads it. Zod is the source of truth; contracts/dossier.schema.json and docs/CAPABILITIES.md are generated from it so they cannot drift. The gate invariant certificate.status !== "PROVEN" -> the approval gate is never offered is enforced by the type system rather than by a conditional. The Approve control takes an ApprovalGrant, which carries a module-private symbol only openGate() can mint, so there is no value a caller could pass to render an approval for an unproven change. gate.typetest.ts asserts four forgery attempts are compile errors; weaken the type and tsc fails the build on an unused @ts-expect-error. Two deliberate refusals to trust: - The verifier's own checksums.match flag is never believed. openGate recomputes pre === post_rollback itself, so an engine bug or a forged payload cannot open the door. - An exclusion with an empty reason fails to parse. An exclusion without a stated reason is an omission, not an exclusion. detectors.ts is the only thing in the system that can light a lamp on the Harness Panel. It folds real TrueForge events and reads both camelCase and snake_case, since the SDK and the HTTP surface disagree on spelling. 34 tests. The gate suite proves exhaustively that no combination of kind, status and viewer role opens a non-PROVEN certificate. The detector suite proves the inverse: noise lights nothing, one connector seen twice is not two connectors, and prose that merely mentions a chart is not Generative UI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three named zones, because the Savile Row rubric names three things:
DOING (live run, subagent lanes with model and cost, streaming sandbox
log), WAITING (the approval queue, what each change is blocked on and for
how long), DID (the immutable ledger).
The console IS the SDK, not a lookalike beside it. TrueForgeUI accepts a
custom layout rendered inside its own provider stack, so AirlockConsole is
passed as layout={} and the transcript, composer, thread list, approval
cards and MCP OAuth screen are all trueforge-ui's own components rethemed
through semantic tokens.
The Harness Panel lights only from real events. observedServer.ts wraps
the real adapter's createTurn and observes each event on its way past,
yielding it onward unmodified — never synthesised, re-ordered or dropped.
A run that does not exercise a capability ends below 22, and unlit rows
stay legible, because hiding what did not happen would make the counter
meaningless.
The Certificate card renders the checksum triple as the argument made
visible: lines 1 and 3 bracketed together, line 2 de-emphasised because it
is expected to differ, and on a mismatch the exact character where the
hashes diverge highlighted rather than a red X. When the gate is sealed no
approval control is rendered at all — the sealed door says so, and names
the reason.
The gate runs again server-side in dossierStore.decide(), against the
stored dossier and a viewer resolved from TrueForge's own /auth/me. The
role is never read from the request body: if a client could name its own
role, separation of duties would be decoration.
Three fixes found by actually running it:
- Custom layouts must mount ComposerBusyProvider themselves. It is wired
by default in <Thread /> only, so composing ThreadContainer and
ComposerContainer separately throws on mount. Undocumented.
- trueforge-ui/styles.css ships a full Tailwind utility set in
@layer tfy-agent-ui-utilities. Imported after tailwindcss it registers
as a LATER layer, so its plain .hidden beat our .xl\:flex regardless of
the media query — silently breaking every responsive variant in the app.
Fixed with an explicit @layer order statement.
- The harness rail is docked from xl up and a closed drawer below, so it
does not cover the certificate on a tablet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The root agent mounts every production connector read-only and puts the single write path behind require_approval_for_tools. Because subagent tool calls still pause for approval, no principal in the run — root or subagent — can reach production without a human. That is the honest form of "subagent tool scoping" given the harness spawns subagents dynamically and shares tools with them. airlock-scout is per-task model routing at the boundary that actually supports it: reconnaissance (repo scans, row counts, release notes) does not need the model that authors a migration and should not cost what it costs. Three git-backed skill packs carry the knowledge the model must not improvise: - postgres-safety: what is genuinely irreversible, which DDL takes which lock, when a rewrite happens, and why a non-injective UPDATE (ROUND, integer division, LOWER, lossy casts) cannot be rolled back. Version differences at 11/12/13 are called out because reasoning about lock behaviour from memory is how a "safe" migration takes a table offline. - expand-contract: the three-phase pattern, with an exit condition for each phase. A recommendation without an exit condition is a delay. - data-retention: how to scope an erasure across systems, and which records survive it. Stripe keeps charges; Slack keeps messages others are party to; a delete marker is not a deletion. Disclose all of it in the exclusion list rather than implying the data is gone. The README states plainly which three planned capabilities rest on API that does not exist, what was built instead, and which detectors are unverified — an honest 19/19 beats a padded 22/22 that a judge disproves by clicking one lamp. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The certificate answers "is this change what it claims to be". It cannot answer "is it allowed, by whom, and right now" — that is a property of the organisation, not of the change. So the gate now evaluates both. policy.ts ships a real, opinionated default rather than an empty framework, because a policy with no defaults is a form. Seven change classes, each with a required certificate kind, a quorum, a freshness window, ceilings on records, people and money, and change freezes evaluated in wall-clock time with Intl rather than in UTC. Four rules are worth calling out: - A proof is perishable. Past its freshness window a certificate describes a system that no longer exists. - Production is re-checksummed before the gate opens. If it moved since the proof was taken the change is sealed — even when the drift checker itself reported everything was fine. A claim of danger is believed; a claim of safety is recomputed. Same asymmetry as checksums.match. - A quorum counts people, not clicks. Signatures are stored by identity, so the same approver twice is one approver, and the requester is never one. - No production access may be granted without an expiry, so the default state of the system is that nobody has the keys. Break-glass exists and cannot become an approval: BreakGlassOverride carries a different private symbol, so no function accepts both. People go around sealed doors during incidents whether or not we let them; a control plane that pretends otherwise does not prevent the override, it only ensures there is no record of it. Off by default in two places. receipt.ts hash-chains decided changes. This does not make the ledger unforgeable — anyone who can rewrite the file can recompute the chain. It makes tampering *visible* to anyone holding an older copy of one hash, which is the property that matters, because the person auditing you is not the person who edited it. Built on Web Crypto so the same function verifies on the server and in a reader's browser. The gate's verdict banner now has a distinct headline per seal reason. A banner reading "no certificate" over a change that has one, and whose digests merely disagree, is a small lie, and that component exists to be believed. Tests go 34 -> 83. They pin properties, not implementations: no non-PROVEN certificate opens the gate under any combination of class, status and viewer; freezes are correct in British Summer Time; editing a sealed record is caught at the record where it happened; no two seal reasons share a banner. check-fixtures.mjs asserts every fixture produces the gate verdict its filename implies, so a fixture named .standing.json really is refused for having no expiry rather than for something nobody noticed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Least privilege was previously a configuration claim: production connectors
mounted read-only, the write path listed in require_approval_for_tools. True,
but it depended on every connector being configured correctly, forever, by
everyone.
AIRLOCK now ships as an MCP server, and the agent's entire vocabulary towards
production is five tools: read the policy, open a change, attach a
certificate, check the gate, ask a human. There is deliberately no tool that
applies a change, so the dangerous state is not "misconfigured" — it is
unrepresentable. The same move the gate makes in the type system, made again
in the tool surface.
{ "name": "airlock",
"enable_tools": ["@ALL"],
"require_approval_for_tools": ["airlock_request_approval"] }
Two details make it hold rather than merely read well. MCP tool annotations
are what TrueForge's @read-only and @destructive selectors resolve against,
so they are set on our tools too. And because TrueForge subagents inherit the
root agent's MCP scope — the thing that makes per-subagent tool scoping
impossible — no subagent can acquire a path we did not give the root.
The protocol is hand-written rather than pulled from an SDK, ~250 lines of
JSON-RPC over stdio. The whole argument is that the agent's only route to
production is a tool you can read in an afternoon, so the file defining that
tool should be one you can read in an afternoon.
Four agents now, routing at the boundary since TrueForge cannot route per
subagent: change-control, scout, privacy (erasure work, no infra tools, leans
towards retention), treasury (payments only, temperature zero, iteration
limit 40 because a treasury agent still exploring on its fortieth step has
lost the thread).
Four new skill packs at the depth of the existing three: access-review
(effective permissions, not policy documents — PassRole, default privileges,
BYPASSRLS), money-movement (derived idempotency keys, disputes, minor units),
comms-blast (suppression, dedup by person, sender reputation, quiet hours),
infra-safety (quorum arithmetic, DNS TTL, add-then-remove rotation, and why
"unused" usually means "broken").
check-agents.mjs asserts the privilege model rather than describing it: an
agent is acceptable in exactly one shape — it mounts AIRLOCK and holds
precisely one tool for approval, or it has no write capability at all. The
dangerous middle is rejected. Eleven tests cover the server itself, including
that exactly one tool is annotated destructive and that no tool is named
apply, execute, run, commit, deploy or merge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three surfaces now, one design system. A product whose front door is built from a different design system than its interior is a product that was built twice. / is the argument, laid out as a specification document — numbered sections, a rail that tells you where you are, hairlines instead of cards. Two places in it let the reader operate the real thing rather than be told about it: - §02 builds a real Change Dossier from a set of controls and passes it to the real openGate(). Every combination is a live evaluation. Break the checksum triple and the Approve control does not grey out — it stops existing, because ApproveControl takes an ApprovalGrant and a sealed decision does not carry one. Time is pinned to a fixed instant so the same inputs give the same answer on the server and after hydration. - §07 seals three records into a hash chain with Web Crypto and invites you to rewrite one. The verification runs in the reader's browser, which is the point: a tamper check performed by the system holding the data proves rather less than one performed by the person who does not trust it. §06 shows all 22 capability lamps dark, and says why. Lighting them on a marketing page would make the panel a graphic. /control is the other audience — not "should I approve this one" but "what is this system holding, what has it refused, and can I still trust the record of what it did". Its headline number is what the gate refused, not what it approved: a queue with nothing in it is not evidence of safety, and a count of changes stopped, with reasons, is. The certificate card grows the blocks the contract earned: magnitude (people in the alarm colour, records never — forty thousand rows is a Tuesday), the policy in force and its objections, signatures with a quorum meter, the drift comparison, principals for access grants, and the receipt. Approve reads "Countersign — 1 of 2" when the signature would not move anything, because a button that implies otherwise is lying. Break-glass is rendered only where policy and deployment both allow it, as a dashed outline under the sealed door rather than a second primary action, and it demands forty characters of written reason before it will submit. Server-side, decide() recounts the quorum from stored signatures rather than believing the grant's own `final` flag — a flag on an object the client touched is a claim, not a fact — and seals the record into the chain when it closes. Seeded fixtures are re-based to the current time so certificates are always fresh; decided records are not, because their receipts commit to their timestamps and moving them would break the chain the demo invites you to verify. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The four claims on the front page are now checked mechanically on every push, so none of them can quietly stop being true: no non-PROVEN certificate opens the gate, a sealed record cannot be edited unnoticed, every fixture is refused for the reason its filename claims, and no agent can write to production without holding the approval tool. The generated-artefact step is the fifth and the one most likely to catch us out: docs/CAPABILITIES.md, docs/POLICY.md and contracts/dossier.schema.json are emitted from the source, so a drift between what we claim and what we do fails the build rather than surviving until a judge notices it. docs/DEMO.md is the runbook for the three-minute video, timed, with the six refusals to fire at the live gate in order and the exact words for each. It also carries the curl sequence, which is the most convincing twenty seconds available because it happens with no browser involved. docs/SUBMISSION.md is the written description plus, at the end, what is honestly not done — the fixtures are fixtures, three detectors are unverified against a live harness, and hosted mode has not been exercised on a Windows development machine. A judge finds that list either way; finding it here is much better than finding it on screen. TRUEFORGE-NOTES §4.2 records why the MCP server made the least-privilege claim stronger, including the part worth remembering: the inheritance rule that makes per-subagent tool scoping impossible is exactly what makes a single gated doorway airtight. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI failed with "Generated files are stale. Run 'npm run gen'", and the diff it
printed was:
packages/mcp/bin/airlock-mcp.mjs | 0
1 file changed, 0 insertions(+), 0 deletions(-)
Zero insertions, zero deletions: a file mode change, not content. The bin has a
shebang and is declared in package.json, so `npm ci` chmods it to 755 on Linux.
Git on Windows does not record the exec bit, so it had been committed 100644,
and the step — which diffed the entire working tree — attributed an unrelated
environment artefact to the generated docs.
Nothing was stale. The check was wrong, and its failure message sent the reader
to the wrong file, which is worse than not having the check.
Three fixes, because fixing only the first leaves the trap armed:
- The bin is now tracked 100755, so the index and the installed tree agree on
every platform.
- The drift check is scoped to exactly the four paths the generators write, and
runs under core.fileMode=false. Anything else the build dirties is reported
separately, as a warning, under an accurate heading.
- .gitattributes normalises line endings to LF, and a renormalise pass converts
ten files that had gone in as CRLF. Those came from editing through Python on
Windows, where io.open().write() translates \n to \r\n by default — invisible
locally, and a standing source of exactly this kind of phantom diff on a Linux
runner. `git diff --ignore-all-space` confirms zero content change in all ten.
Also bumps checkout and setup-node to v5, which clears the Node 20 deprecation
warnings on the runner.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps closed, both found by looking rather than assuming. THE TAP WAS UNTESTED harness.test.mjs covered the detectors in isolation — given this event, does that lamp light. Nothing covered the seam they hang from, which is where the credibility of the whole panel actually lives. A passthrough that quietly dropped one event in fifty would still render a perfectly plausible console, nobody would notice, and every number on the panel would be wrong. The pure part of the tap moves to packages/contract/src/observer.ts, generic over the chunk type with no dependency on the UI SDK, so the invariant is testable without standing up a chat server. observedServer.ts is now the twenty lines that wire it to createTrueForgeAgentUIServer. observer.test.mjs asserts fidelity first — same chunks, same objects by identity, same order, none added, none lost, even when a detector throws or the transport dies mid-stream — and then drives a realistic turn stream through the real tap into the real ledger. It checks the lamps both ways: the thirteen the run earned, and the five that must stay dark. Also that a replayed stream does not double-count, and that a stream alone cannot reach 22, because five capabilities are established by configuration or observed behaviour and no amount of streaming can produce them. The stream is built from the documented schema in TRUEFORGE-NOTES §5 and mixes camelCase and snake_case deliberately, since the HTTP surface and the SDK disagree and a tap that understood one would light half a panel on the wrong transport. It lives only in the test: there is no replay mode in the console, because a panel that can be lit from a recording proves nothing. 106 -> 0 CONTRAST FAILURES An axe-core pass over all three routes found 106 nodes failing WCAG 2.1 AA: legends, hints and secondary evidence, everywhere. --ink-3 was at 3.03:1 and --ink-4 at 1.57:1 against a required 4.5. They had been chosen for the mood they created rather than measured, and "it is dim on purpose" is not a defence anybody has to accept. Lifting only those two would have pushed ink-4 above where ink-3 had been and collapsed four steps into two, so the whole scale was rebalanced. Every step now clears 4.5:1 on every surface it can sit on, and adjacent steps stay 1.37x-1.64x apart in relative luminance so the hierarchy still reads. The unlit lamp got its own token in the process. It had been sharing --ink-4, so raising the text scale to pass AA would have made every unexercised capability look exercised — the one thing that panel must never do. An off lamp is a graphic with the opposite requirement to text, and tying the two together was a latent bug waiting for exactly this change. check-a11y.mjs is committed so the result is reproducible rather than a claim. It is deliberately not in `npm test`: it needs a built console, a running server and a downloaded browser, and a check that is flaky for environmental reasons trains people to ignore it. Tests 94 -> 106. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two surfaces still carried the old ink values after the contrast rebalance. global-error.tsx is the important one. It renders the digest at #626d7d — the old ink-3, 3.03:1 — and it inlines its colours on purpose, because it displays when the app and therefore the stylesheet have failed. That is exactly why it drifted without anyone noticing: an accessibility audit of the happy path can never reach the one screen you most need to be able to read. Every element on it now clears AA, the lowest being the alarm line at 6.29:1. The SDK theme tokens in AirlockShell were still passing #98a2b2 for textSecondary and ghostButtonText. That value passes AA on its own, so this is not a fix so much as a refusal to run two ink scales in one console and call it one design system. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ran AIRLOCK against TrueForge 0.1.4 in Docker with a live OpenAI key. Three assumptions in the agent specs turned out to be wrong, and one of them would have failed silently in the worst possible way. MCP SERVERS ARE REMOTE ONLY `MCPServerType` is an enum with exactly one member: "remote". There is no stdio transport, and no `command`/`args`/`env` anywhere in the API. The specs' `npx -y @airlock/mcp` could never have worked — and it would not have errored. TrueForge would have accepted the agent, the connector would have had no tools, and airlock_request_approval simply would not have existed. The entire human-in-the-loop guarantee, missing, with the console still looking correct. So @airlock/mcp gained a Streamable HTTP transport beside stdio. Both go through one `respond()` so they cannot drift into answering the same request differently. Verified: the harness enumerates all seven tools and reads their annotations, which is what @read-only and @destructive resolve against. airlock_request_approval {"readOnlyHint":false,"destructiveHint":true} THE PROOF THAT MATTERS A real turn, on the real harness: turn.created mcp.initialize model.message -> airlock_check_gate model.message -> airlock_get_change model.message -> airlock_request_approval tool.approval_required <- the harness held it turn.done requiredActions:[{type:"tool.approval_required"}] The agent could not proceed. Feeding those events through the real detectors lights capability 1 and capability 13 — the human approval checkpoint, proven by a live event rather than a fixture. An earlier run is worth as much: asked to request approval on a change whose certificate had aged past its freshness window, the agent checked the gate, got CERTIFICATE_STALE, and refused — citing the policy and declining to spend a human's attention. The rule held without being told to. THE REST - Specs retargeted to openai/gpt-4.1 and gpt-4.1-mini. gpt-5-mini rejects the `temperature` and `max_tokens` these specs send (400); the 4.1 family accepts them, which keeps the treasury agent's temperature-zero design intact. Two distinct models so per-task routing is still real on one provider. - The console now pins the agent for every session it creates. Left alone the SDK opens a session on a bare model with no connectors — a perfectly good chat window and a completely useless airlock. - scripts/register-agent.mjs reconciles a canonical spec with what a server actually supports and prints every adaptation, instead of keeping a second, degraded copy of each spec that would rot. It refuses outright if the gated tool would be dropped. - Dead `command`/`args` keys and a machine-specific absolute path removed from the specs. TRUEFORGE-NOTES §4 grows from three corrections to six: remote-only MCP, the sandbox being Daytona-or-nothing (which makes capabilities 5, 6 and 7 unreachable without an account, and takes skills with it), and the SDK composer's getSnapshot defect being a functional break against a live server rather than a development warning — typing a message creates no session at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The port clash you hit was the least of it. Chasing it uncovered a chain of
failures that had been silently making the console unable to talk to TrueForge
at all, and each one masked the next.
1. PORT CLASH — not ours to kill
5433 and 6380 were held by vivedly_cache_db and vivedly_cache_redis, an
unrelated project of yours that was up and healthy. So the harness now lives in
`harness/` and publishes nothing but 8791: only the server talks to Postgres and
Redis, over the compose network, by service name.
It builds the published npm package rather than TrueForge from source, because
`git clone` of that repository cannot complete on Windows — ~1700 files fail on
filename and path constraints, and the build then dies compiling the SDK against
a half-empty tree. This Dockerfile copies nothing from the context, so the host
filesystem is irrelevant. `npm run harness:up`.
2. THE CONSOLE NEVER READ YOUR .env
Next reads `.env` from `apps/console`, and there isn't one. Every other tool in
the repo reads the root `.env`, so the file was visible to everything except the
one process that needed it. `TRUEFORGE_BASE_URL=8791` was ignored and the
console used its `:8790` default; `AIRLOCK_BREAK_GLASS=1` was ignored so the
control never appeared. Mutating process.env from next.config.ts does not fix
it: the server runtime does not inherit the mutation.
3. AND MY PARSER WAS WRONG — this one is mine
Even once it found the file, it read three keys out of eight. In JavaScript a
carriage return is a line terminator: `.` will not match it and `$` without `m`
cannot anchor past it. So on a `.env` with CRLF endings,
TRUEFORGE_BASE_URL=http://localhost:8791\r
silently fails a regex that looks obviously correct. The only line that parsed
was the empty one, where `\s*` happened to swallow the CR. Split on `/\r?\n/`.
`/api/config` now reports which file it read and which keys it found, because a
console pointed at the wrong server looks exactly like one pointed at the right
server until something fails.
4. TRUEFORGE SENDS NO CORS HEADERS
`OPTIONS` returns 404; a GET with an Origin comes back with no
access-control-allow-origin. A page on :3000 cannot call :8791 at all — every
request dies as ERR_FAILED before arriving. The console now proxies the harness
at `/harness/*` on its own origin, streaming the body rather than buffering it,
because turn events are SSE and a buffered proxy turns a live console into a
spinner.
That is the right shape regardless: the harness need not be reachable from a
browser, and credentials can be attached server-side.
5. THE getSnapshot LOOP, EXPLAINED
Every SDK catalog method ends in `response.json()` — a new array per call — and
they are read through useSyncExternalStore, which compares by identity. So every
snapshot looks new and React loops forever.
It only fires when those calls SUCCEED. While the console was on the wrong port
they all failed, the stores stayed empty, and the bug looked like a harmless
warning. Fixing the connection turned it into a crash. `withStableCatalogs`
memoises the read-only catalogs by serialised value and returns the identical
reference when nothing changed, which stops it being fatal. The composer still
remounts intermittently; this is a mitigation, not a fix.
ALSO
- Sessions the console opens now run the AIRLOCK agent, so the gate is in the
loop. `npm run harness:setup` does the provider, connector and agent
registration in one command and refuses if the gated tool would be dropped.
- The fault screen's stack trace is keyboard-reachable. It only appeared in the
a11y audit once the console started faulting, which is the one moment someone
is least likely to have a working pointer path to it.
TRUEFORGE-NOTES §4 goes from six corrections to eight.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Updating policy.yaml and pushing new changes.
# Conflicts: # apps/console/next.config.ts # package.json
Completes the three features begun in the preceding commit, which landed under the message "New features" from outside this session and so does not describe what it contains. For the record, the two commits together are: ABORT — the third pillar The brief AIRLOCK answers names three failures: the agent cannot reach your tools, cannot run its own code safely, and cannot be stopped before it does damage. An approval gate covers the third only in the sense that it stops a change before it starts. Nothing stopped one already running. There is now a red kill control on any live turn. It goes through the harness rather than the browser dropping a stream, because the work is happening on an executor somewhere else — TrueForge peers cancellations between executors over Redis, so it lands on whichever replica is actually doing the work rather than merely the one that took the request. That is capability 23, and it lights on `turn.done` with `state.status = cancelled`, which is the only thing that proves a cancel actually arrived. All four agents gained instructions for being cancelled: roll back the open transaction rather than letting the connection drop it, tear down the shadow branch, attach no certificate for work that did not finish. A cancelled change must end up looking as though it never started. POST-APPLY HEALTH CHECK — the Undo Certificate stops being a permission slip The moment a change lands, production is re-checksummed against what the certificate predicted. If it does not match, AIRLOCK executes the rollback it already proved — the same statements, in the same order, that returned the shadow branch byte-for-byte. The refusal is the part worth having. **AIRLOCK will only auto-revert a rollback it has proof of.** A change with no proven inverse raises an alarm and touches nothing, because running an untested rollback against a database already in an unexpected state is how a bad afternoon becomes a bad quarter. `assessPostApply` is pure and exhaustively tested, so the thing that decides is never the thing that executes. `post_apply` sits outside the receipt body deliberately. A receipt seals a decision and the evidence it was taken on; what production did afterwards is a later fact, not a revision. Folding it in would mean re-sealing a sealed record, which is the one thing the chain exists to prevent. `dos_email_unique` is a fixture of exactly this: approved on a good proof, applied, failed its health check when a signup race wrote two more duplicates between verification and apply, and reverted itself in 3.4 seconds. POLICY AS CODE `airlock.policy.yaml` at the repository root is read at runtime and validated before it goes near the gate. Edit it, restart, done — no rebuild. Validation is not a formality. A typo'd key does not tighten a rule, it removes one: `max_peple: 1000` is an absent ceiling with a spelling mistake, and nobody would find out until an erasure went through that should not have. Unknown keys are a hard error, and an invalid file falls back to the stricter shipped default while saying so loudly — never to nothing. New with it: `max_lock_ms`, a ceiling on how long an operation may hold a lock. A lock is not a magnitude; it is a duration during which every other query queues behind yours. Five seconds for a migration, two for a bulk data operation. `dos_orders_backfill` trips it at 9.48s and is refused with "REFUSED — THE LOCK IS TOO LONG", which is a second refusal type that makes the gate structural rather than a single trick. `check-policy.mjs` asserts the YAML resolves identically to the shipped default, so the console enforcing one thing while the docs and tests describe another fails the build. It also asserts the rules that must never exist: no change freeze on erasure, money or access, and self-approval not configurable to on. Tests 107 -> 118. Fixtures 11 -> 13, now covering seven distinct refusals. Capabilities 22 -> 23. Still clean against WCAG 2.1 AA. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`check:policy` went missing when the two branches were merged. Without it the YAML policy and the shipped default can drift silently — the console would enforce one thing while the docs, the landing page and the tests describe another, and both would look correct. Also asserts the rules that must never exist regardless of what the file says: no change freeze on erasure, money or access, and self-approval not configurable to on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The drift check caught this one correctly: `PostApply` was added to the contract and `contracts/dossier.schema.json` was never regenerated, so the JSON Schema the Python verifier reads did not know the field existed. That is exactly the failure the step is for — the generated artefacts are what a second implementation builds against, and a schema that silently lags the TypeScript is how two halves of a team end up disagreeing about a contract they both think they share. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Updating features and code base to fix bugs.
The three features landed in the previous commit (swept in by an external
tool under a generic message); this is the documentation they were missing,
plus the counts that had gone stale.
Corrected throughout: 165 tests not 92, 14 fixtures not 11, 23 capabilities
not 22. A submission document that overstates its own test count is the
last place you want a judge to start checking.
README gains three sections, each written around the property that makes the
feature defensible rather than the feature itself:
- provenance, because a checksum the sandbox measured and a record count
the agent asserted about its own change should not render identically,
and on every dashboard we have seen, they do;
- the undo window, because the proof has a second life — the inverse was
already run against a shadow copy, so for as long as production has not
moved on it is still known-good, and that is the only reason a one-press
undo on a production database is responsible rather than reckless;
- the budget cap, which is deliberately not a new kill switch. It pulls
the lever ABORT pulls, so it lands on the executor rather than in the
tab that noticed.
DEMO.md gets the beat that is hardest to argue with on camera: dos_plan_column
applied cleanly, passed its health check, and was taken back anyway eleven
minutes later because finance's nightly read the dropped column. Every
checksum agreed. It was still the wrong change, and no health check was ever
going to catch it.
Also documents the four undo refusals with the curl that reproduces each, and
records that the window is judged on the server — a press arriving after it
closes is refused with the closing time quoted back, however much time the
countdown appeared to have.
Verified before committing: 165 tests, 14 fixtures, 4 agent specs, policy
identical to the shipped default, generators idempotent, typecheck clean
across three workspaces, and 0 failing nodes against WCAG 2.1 AA on all
three routes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"The agent read the live schema" is a claim about a local SQLite file until a
real connector is mounted. This is the script that turns it into a fact, and it
is a script rather than a curl in the README because of two things about
TrueForge 0.1.4 that are not guessable from outside:
- PUT /api/v1/settings/mcp-servers is collection-level. There is no
per-name route; sending one returns "Route not found", which reads like a
missing feature rather than a wrong URL. The name travels in the body.
- A remote server registered with no `auth` block is assumed to need no
credentials. The harness connects unauthenticated, the upstream answers
401, and the agent is told "Failed to list tools" -- which names neither
the cause nor the fix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wiring up ESLint reported 23 findings that had never been run against. None
was cosmetic; they are all the React rules about doing impure work while
rendering, and two of them were real bugs.
The genuine defects:
- GateDemo mutated two refs during render to count how many gate
combinations the reader had tried, then read `.size` back out of them in
the same pass. React is free to render a component twice for one
interaction or abandon a render half-way, either of which quietly inflates
a number the page presents as a fact. Counted in the knob handler instead.
- LedgerDemo re-verified the hash chain without a staleness guard, so
tampering with a record while a previous verification was still awaiting
could land the older verdict last and paint the chain green over a broken
link -- precisely the thing that demo exists to show cannot happen.
The rest are correctness-by-construction:
- Both Reveal components set state synchronously inside an effect to handle
the no-IntersectionObserver fallback. They now observe from a ref callback
with a React 19 cleanup, so the observer's whole lifetime is one function
and the element is never left hidden by an effect that has not run yet.
- AirlockShell primed a ref during render to build the RunStore once. A lazy
useState initialiser says the same thing and is safe under concurrent
rendering.
- Zones and the retry countdown read the wall clock while rendering. Both now
hold the instant in state and derive the display from it.
- The two poll callbacks were `async`, whose bodies run synchronously up to
the first `await` -- so calling one from an effect put its state writes on
the effect's own tick. Both are promise chains now.
- The console's "follow the run into the zone that matters" and "open the
sandbox pane" effects adjust state during render against the previous
value. An effect does it after the browser has painted the old view, which
the operator sees as a frame of the wrong zone and then a jump.
- ControlRoom memoises `viewer`; the fallback literal was a new object every
render feeding a useMemo dependency list, so the memo recomputed on every
tick of a six-second poll.
Verified in a real browser, not just by the linter: the a11y sweep passes on
all three pages, the gate counter increments only on combinations not seen
before and holds `opened` flat when the gate seals, the scroll reveals still
fire, and no page logs an error.
Also replaces a real Supabase project ref in a usage example with a placeholder.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… author's
The console was tested thoroughly as a set of functions and not at all as a
running server, and both of the defects that would have reached a judge lived
in that gap.
A fresh clone could not approve anything. Roles come from the harness, and with
no harness configured viewer resolution fell back to `requester`, so every
approve control was absent and DEMO.md's centrepiece could not be performed by
anyone who had not already set the project up. It worked here because this
machine has a .env pointing at a running TrueForge, and nowhere else.
Standalone mode separates two cases that had been collapsed: nothing configured
at all now resolves a clearly-labelled `local-operator` who may approve, with a
banner the console will not let you dismiss saying there is no identity provider
and this is not separation of duties; a harness that *is* configured and does
not answer still resolves `requester`. The audit property — a dropped container
must never mint an approver — is preserved exactly, because that path is the
configured-but-unreachable one.
A decision verb was matched with a defaulting ternary, so every input that was
not the literal string "rejected" approved the change and answered 200:
`{"decision":"reject"}`, the wrong case, a null, an empty body. Every other
mutation on a dossier already failed closed. This was the one that guarded
production. It is an exact match and a 400 now.
Also fixed, each found by running the thing rather than reading it:
- The README's forged-dossier transcript, introduced as "transcripts, not
illustrations", could not run against a production server: writing a dossier
needs a machine token and the documented command never sent one.
- Pressing an example prompt with no harness replaced the guided start with a
dead "How can I help you today?" and no explanation. The console now says
which half of itself still works, and keeps saying it after the press.
- Fixture discovery walked up from cwd, so a console started from anywhere but
the repo seeded an empty queue silently. It resolves from the module first,
and AIRLOCK_FIXTURES_DIR overrides both.
- /api/activity reported `reachable: false` for an idle but perfectly healthy
harness, and its own note admitted it could not tell the difference.
- The live activity feed scrolled with no keyboard path — the same defect
already fixed on the fault screen. WCAG 2.1 AA is clean again on all three
routes.
- DEMO.md said nine changes waiting (thirteen), and labelled dos_erasure_dana
as "Countersign — 1 of 2" when its one held signature makes your press the
final one and the control an armed destroy. README said 263 tests (345).
Two new layers close the gap that hid all of this:
- scripts/check-console-http.mjs boots the built console in three identity
postures and replays every documented interaction — the README's curl
transcripts, DEMO.md's verdicts, the undo states, the refusals. 53
assertions, in `npm test` and CI. It is hermetic by running the child with an
empty cwd, which is what makes "a fresh clone" reproducible on a machine that
has a full .env — the precise reason this survived so long.
- scripts/check-demo-ui.mjs drives 32 interactions in a real browser: the six
refusals in the order DEMO.md gives them, the Approve control ceasing to
exist rather than disabling, both quorum labels, and the ledger demo breaking
and restoring its own chain. In CI.
check-console-security and check-secrets ran in `npm test` but were never
enumerated in CI, so the one place they were guaranteed to run was a laptop.
Both are wired in.
Carries the in-flight harness and verifier work in the same commit: the Postgres
verifier, the skills and approval harness scripts, connector and agent
registration, and the magnitude tests. Splitting it would produce an
intermediate commit whose claims table does not match its code, and this is the
tree that was verified.
345 tests, 0 fail · 18 fixtures · 32 claims anchored · 53 HTTP assertions ·
32 browser interactions · 0 accessibility violations · lint and typecheck clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three faults stacked into one symptom: a console opened on a checkout used more than half an hour ago showed every seeded change sealed CERTIFICATE_STALE. The gate was right — the certificates genuinely had expired — and the product looked broken, with the one human moment it exists for unreachable. - `freshen.ts` was written and tested but never re-exported from the contract's barrel, so `freshenFixtures` and `fixtureAgeSeconds` were not on the package's public surface. `scripts/demo-refresh.mjs` imports both and threw `TypeError: fixtureAgeSeconds is not a function` on every invocation. - `demo-refresh.mjs` says in its own header that `npm run up` runs it. Nothing in up.mjs called it. It now does, as its own step, before the console starts rather than after — the store caches the ledger on first read, so re-dating the file behind a serving console leaves the console showing the copy it had already loaded. - The failure is reported rather than swallowed. It touches demo data only, so it warns instead of stopping, but it says so: `up` previously printed a clean bill of health over a stack whose every fixture was two days stale. Found by launching the stack and clicking through it. On this machine the fixtures were 51 hours past their freshness window and `up` reported success. Also: queue rows now carry `data-dossier-id`. Two different changes can be worded identically — a console pointed at a real harness accumulates several "give the on-call engineer read access" requests — so request text does not identify a row, and check-demo-ui was matching whichever one happened to be last in the DOM. It addresses rows by id now, and a fixture that is absent from WAITING because somebody already decided it is reported and skipped rather than hanging for thirty seconds on a Playwright timeout and reading as a fault. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every live run died in one of two places, and neither was the gate. 1. The wrong two models were registered. TrueForge ships no model catalog — /api/v1/models returns exactly what harness-setup.mjs registers — and it registered the 4.1 family. Measured on this account: gpt-4.1 is 30,000 TPM, gpt-5.2 and gpt-5-mini are 500,000. A change-control iteration costs ~8.1k input tokens, so the most token-hungry agent in the system was pointed at the narrowest pipe on the key and was throttled every third or fourth step. The recorded reason for avoiding gpt-5 — that it rejects temperature and max_tokens — was true of the earliest preview and is not true now; re-measured before changing it. resume.ts still handles 429s and is simply no longer load-bearing. 2. AIRLOCK held a read for human approval. Supabase's read-only execute_sql still carries destructiveHint:true, so require_approval_for_tools: ["@Write","@destructive"] stopped every run at the first fact it looked up — before opening a change, before any certificate, before the gate. The hold bought nothing: the connector is read_only=true and Postgres refuses the write itself with 25006. Gating a read on top of that did not add a control, it moved the failure earlier. airlock_request_approval is still held, and check-agents.mjs still fails the build if it ever is not. Then the demo had nothing behind it. The live project held one table, four columns, 5,000 rows — and no users.plan_name, which is the change the README, the landing page and the video all promise. Asked to do it, the agent read the schema, correctly found no such column, and stopped to ask a human. The product working; the demo dying on camera. - seed-supabase.mjs seeds the schema the demo actually talks about: 1,000,000 rows across six tables, 135 MB, RLS on with no policies. It uses a human's management credential, never the agent's read-only connector. - docs/DEMO.md is replaced. The old one was a click-through of hand-written fixtures; every impressive number came from a JSON file. - npm run demo runs three acts live: a change that cannot be undone and is therefore never put in front of anyone, the expand/contract version that proves byte-identical and stops for a human, and the human decision sealed into the hash chain. It refuses to run at all if the harness or the database is missing, because a demo that degrades gracefully is a demo that lies. Also fixes a check that could only ever pass once per machine: check-demo-ui presses the countersign control it is asserting, so the next run found one of two signatures held, correctly declined to offer the same person the second, and failed. demo-refresh now restores shipped signatures as well as dates, and check:demo:ui runs it first. Green twice in a row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two instructions were describing a product that had changed underneath them, and both cost a real run several wasted steps and one honest-looking failure. The verifier runs the proof inside a throwaway schema whose search_path is that schema and nothing else, and refuses a schema-qualified statement rather than rewriting it — correctly, because a certificate for a statement nobody is going to run is worse than no certificate. Nothing told the agent that. It reads the live schema as `public.users`, which is right, then writes `alter table public.users ...`, which is refused. Every live run hit this, attached a FAILED certificate, and reported that the change could not be proven — a true sentence about nothing. Step 5 of the order of operations still said "airlock_attach_certificate with the result". That predates airlock_verify_change, which now measures the checksums and writes the certificate itself. The agent dutifully attached one afterwards and got refused: a PROVEN UNDO certificate can only be minted by the thing that measured it. Correct refusal, wasted call, confusing transcript. The instructions now say both, including what the shadow cannot execute (CONCURRENTLY, and anything whose reversibility depends on a constraint, index or trigger the `create table ... as select` copy does not carry). A live run of the demo migration now goes: read policy, read live schema, open change, PROVEN on the first attempt, gate would open, held for a human. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`contracts/dossier.schema.json` carried a `default` block on `magnitude` that the generator no longer emits, so the "generated artefacts are up to date" gate had been failing on every push since before this branch of work. Regenerated; nothing else moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Damir cannot test this from a clone. `.env.example` documented neither of the
two things the live path requires:
- no Supabase section at all, so `npm run seed:supabase` fails with "Need
SUPABASE_ACCESS_TOKEN and SUPABASE_URL in .env" against a file that has
never mentioned either;
- no model key section, so the agent path has no documented starting point.
Both are now in .env.example, explained where they are set rather than in a doc
somebody has to know exists. The Supabase note says plainly that --reset DROPS
six tables, points at --check as the safe first command, and spells out why
there are two different credentials: the seeder writes with a human's admin
token from a terminal, the agent reads over read_only=true where Postgres
refuses writes itself.
DEMO.md gains a second-machine section with the five commands in order, and the
README's "Run it" links to it. Both state the thing worth knowing up front:
`npm run demo` needs no model key at all. It drives AIRLOCK's own MCP server, so
the demo cannot fail for a reason that has nothing to do with the product — only
`harness:turn` needs a key.
Verified by cloning this repo fresh from GitHub into a clean directory:
npm install, build, and test:fast all pass, 345 tests, exit 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hero was built from a visual reference: a cartoon retro computer at the
centre, a smiley face, a camera and an envelope drifting around it, and a giant
white pixel wordmark behind. It was carefully executed and it was selling a
different product. This one asks to be trusted with a production database, and
emoji do not survive contact with that claim -- the tone said toy while the copy
said root access.
Two more things a screenshot makes obvious once you look for them. The wordmark
sat in white on light grey at roughly 1.2:1, overlapping the object, so it read
as texture rather than as a word. And nothing above the fold said what AIRLOCK
is: "proving every irreversible change first" describes a feeling, and a reader
who does not already know what a shadow copy is learned nothing before deciding
whether to scroll.
So the hero is now the product's own output, in two panels. Left, the request as
every other approval flow presents it -- the SQL, the row count, a green Approve
button, and three rows of evidence marked "not attached". Right, the same request
after AIRLOCK ran it: the sha256 triple, line 3 marked as not line 1, GATE
SEALED, and a dashed outline where the approve control would be. The absence is
drawn rather than merely absent, because an absence nobody notices makes no
argument.
The digests are the real ones from Act 1 of `npm run demo` -- dropping
users.plan_name against a shadow copy of the 100,000-row table. On a page whose
whole pitch is that its numbers were measured, a hero illustrated with
sha256:aaaa... would have been the one place it quietly was not.
Two fixes found while checking the result rather than assuming it:
- `check:a11y` failed the first draft: the "not attached" grey measured 3.12:1
on the panel's near-black. Fixed the colour to 4.99:1, not the threshold.
All three routes are clean against WCAG 2.1 AA.
- At 390px the nav's "Open the console" button did not fit, and the plate's
overflow-hidden sliced it in half instead of scrolling -- so the page looked
broken on a phone while correctly reporting zero horizontal overflow, which
is how it survived this long. The button now shortens below sm.
Machine.tsx is deleted; nothing else imported it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The landing page was a separate visual language from the thing it was selling.
The console is "Instrument": five surface elevations, four measured ink weights,
hairlines instead of shadows, and a palette that is a state machine. The landing
page was a light grey plate with a diagonal gradient and a floating pill nav,
matched to a visual reference for a different kind of product. Crossing from /
to /console did not feel like moving through one piece of software, because it
was not one piece of design.
Two consequences, neither cosmetic.
THE PAGE HAD NO COLOUR VOCABULARY. Instrument's second rule is one alarm
colour: --hazard means irreversible and nothing else, and scarcity is what makes
it register. The landing page spent that same orange on its brand, its buttons,
its links and its emphasis. By the time a reader reached a genuine
irreversible-change chip, the colour had been trained to mean "AIRLOCK". So the
interactive accent is now --ice, the console's own, and orange survives in one
place: the mark in the nav, where it is identity rather than status.
THE SECTIONS HAD NO RHYTHM. Nine identical grey plates is a list, not a page,
and each one spent 190px of sticky left column carrying two words while capping
the headline beside it at 18ch — the widest part of the page was empty and the
narrowest was the title. Sections now open with an index and label over a rule
that fades out, in the console's legend voice, and set the title and lede side
by side. The interactive bands drop one surface step into the void instead of
inverting the palette, which is what "you have stopped reading and started
operating this" has to mean on a page that is already dark.
New: 08 / 08, THE INDEX. The colour index, printed — four swatches, what each
one means, and where it is allowed to appear. A product whose whole claim is
that the state of a change is legible at a glance should be willing to publish
its key, and the old page could not have carried this section, which is the
clearest evidence its palette was decoration.
Found while doing it, and worth more than the redesign:
- The landing page said 201 tests and 16 fixtures. The real numbers are 345
and 18, and the README's copies were right the whole time, because
verify-claims.mjs only ever read the README. The comment in that file
explaining why the check exists opens "201 tests was true when it was typed
and wrong a day later" — which is precisely what had happened one file over.
Both the hero and the numbers band are now checked against the same
measurements; verified by breaking one and watching the build fail.
- `lp-serif-em` was used in three headings and defined nowhere, so the accent
each of those sentences was built around silently rendered as plain display
type. Defined, and the five inline italic accents unified onto it.
- The `Drift` component and its keyframes went with the cartoon props they
were animating.
`check:a11y` clean on all three routes, `check:demo:ui` 28/28, `npm test` green,
no horizontal overflow at 390px.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Repointing --lp-orange-b at --ice flattened four semantic colours onto the interactive accent. The policy table rendered SCOPE in the same blue as a link, and DESTROY beside it, on a page whose own Legend section says that colour means "you can press it" and that --hazard means irreversible and nothing else. SCOPE and DESTROY are now --hazard, UNDO and KEEP are --seal. Two uses of --seal-bg as a text colour went with them; a background hex used as foreground was ~2:1 once the page went dark. Hatch.tsx deleted — nothing imported it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… rendered
The page was correct and inert. Unifying it onto Instrument fixed the language
and the colour semantics, but left flat near-black with hairline borders, small
type in large voids, and no suggestion that a person had composed it. A control
room can be austere; a front door has to look like somewhere.
Three decorations, none of which is a shadow and none of which carries meaning,
so Instrument's third rule survives:
- a 56px rule grid, masked to fade before it reaches any text — engineering
drawing texture, which says "measured" for free;
- two very wide radial washes, ice and seal, at 7-13% — the difference
between "dark theme" and "lit room";
- a 1px top-edge highlight on panels, so a surface reads as facing that light.
One of them did not work, and the way it failed is worth recording. `.lp-grid`
drew its lines in a `::before`, and the same div also carried `.lp-lit`, whose
highlight is also a `::before`. One element has one `::before`; the later rule
won and the grid silently lost. Nothing errors, nothing warns, the class sits in
the markup and the rule sits in the stylesheet, and the page just looks flat.
`.lp-glow` survived only because it happened to use `::after`. The grid is now a
real element — a composable decoration should not be a pseudo, because you
cannot stack two of them. Found by reading computed styles, not by looking.
Also: the grid's lines were mixed off --hairline, #1c212b, which is a legible
border ON a panel and invisible AS a line drawn on one. They are mixed off white
at 4% now.
Composition, on the two most expensive empty spaces on the page:
- the hero's right column ran out of content two thirds down and left a 240px
hole under the comparison panels. It now carries the pipeline in one line —
open, prove, decide — which is also the one question those panels do not
answer: they show the before and the after and say nothing about who does
what between them. The third step is a person.
- display type up a step and section padding down one, because a 15px lede in
a 1440px container was losing.
check:a11y clean on all three routes, check:demo:ui 28/28, npm test green, no
horizontal overflow at 390px.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dates Bumps the airlock-npm group with 14 updates in the / directory: | Package | From | To | | --- | --- | --- | | [playwright-core](https://github.com/microsoft/playwright) | `1.62.1` | `1.63.0` | | [yaml](https://github.com/eemeli/yaml) | `2.9.0` | `2.9.1` | | [zod](https://github.com/colinhacks/zod) | `4.4.3` | `4.6.5` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `24.13.3` | `24.13.5` | | [@truefoundry/trueforge-sdk](https://github.com/truefoundry/trueforge/tree/HEAD/packages/trueforge-sdk) | `0.1.3` | `0.2.0` | | [@truefoundry/trueforge-ui](https://github.com/truefoundry/trueforge/tree/HEAD/packages/trueforge-ui) | `0.2.4` | `0.3.0` | | [next](https://github.com/vercel/next.js) | `16.3.3` | `16.3.5` | | [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.8` | `19.3.0` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.18` | `19.3.0` | | [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.8` | `19.3.0` | | [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.5` | `19.3.0` | | [@eslint/eslintrc](https://github.com/eslint/eslintrc) | `3.3.6` | `3.3.7` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.18` | `19.3.0` | | [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.5` | `19.3.0` | | [eslint](https://github.com/eslint/eslint) | `9.39.5` | `10.11.0` | | [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.3.3` | `16.3.5` | Updates `playwright-core` from 1.62.1 to 1.63.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](microsoft/playwright@v1.62.1...v1.63.0) Updates `yaml` from 2.9.0 to 2.9.1 - [Release notes](https://github.com/eemeli/yaml/releases) - [Commits](eemeli/yaml@v2.9.0...v2.9.1) Updates `zod` from 4.4.3 to 4.6.5 - [Release notes](https://github.com/colinhacks/zod/releases) - [Commits](colinhacks/zod@v4.4.3...v4.6.5) Updates `@types/node` from 24.13.3 to 24.13.5 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@truefoundry/trueforge-sdk` from 0.1.3 to 0.2.0 - [Release notes](https://github.com/truefoundry/trueforge/releases) - [Changelog](https://github.com/truefoundry/trueforge/blob/main/packages/trueforge-sdk/CHANGELOG.md) - [Commits](https://github.com/truefoundry/trueforge/commits/@truefoundry/trueforge-sdk@0.2.0/packages/trueforge-sdk) Updates `@truefoundry/trueforge-ui` from 0.2.4 to 0.3.0 - [Release notes](https://github.com/truefoundry/trueforge/releases) - [Changelog](https://github.com/truefoundry/trueforge/blob/main/packages/trueforge-ui/CHANGELOG.md) - [Commits](https://github.com/truefoundry/trueforge/commits/@truefoundry/trueforge-ui@0.3.0/packages/trueforge-ui) Updates `next` from 16.3.3 to 16.3.5 - [Release notes](https://github.com/vercel/next.js/releases) - [Commits](vercel/next.js@v16.3.3...v16.3.5) Updates `react` from 19.2.8 to 19.3.0 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.3.0/packages/react) Updates `@types/react` from 19.2.18 to 19.3.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `react-dom` from 19.2.8 to 19.3.0 - [Release notes](https://github.com/react/react/releases) - [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/react/react/commits/v19.3.0/packages/react-dom) Updates `@types/react-dom` from 19.2.5 to 19.3.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) Updates `@eslint/eslintrc` from 3.3.6 to 3.3.7 - [Release notes](https://github.com/eslint/eslintrc/releases) - [Changelog](https://github.com/eslint/eslintrc/blob/main/CHANGELOG.md) - [Commits](eslint/eslintrc@eslintrc-v3.3.6...eslintrc-v3.3.7) Updates `@types/react` from 19.2.18 to 19.3.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `@types/react-dom` from 19.2.5 to 19.3.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) Updates `eslint` from 9.39.5 to 10.11.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v9.39.5...v10.11.0) Updates `eslint-config-next` from 16.3.3 to 16.3.5 - [Release notes](https://github.com/vercel/next.js/releases) - [Commits](https://github.com/vercel/next.js/commits/v16.3.5/packages/eslint-config-next) --- updated-dependencies: - dependency-name: playwright-core dependency-version: 1.63.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: yaml dependency-version: 2.9.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: airlock-npm - dependency-name: zod dependency-version: 4.6.5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: "@types/node" dependency-version: 24.13.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: airlock-npm - dependency-name: "@truefoundry/trueforge-sdk" dependency-version: 0.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: "@truefoundry/trueforge-ui" dependency-version: 0.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: next dependency-version: 16.3.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: airlock-npm - dependency-name: react dependency-version: 19.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: "@types/react" dependency-version: 19.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: react-dom dependency-version: 19.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: "@types/react-dom" dependency-version: 19.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: "@eslint/eslintrc" dependency-version: 3.3.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: airlock-npm - dependency-name: "@types/react" dependency-version: 19.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: "@types/react-dom" dependency-version: 19.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: airlock-npm - dependency-name: eslint dependency-version: 10.11.0 dependency-type: direct:development update-type: version-update:semver-major dependency-group: airlock-npm - dependency-name: eslint-config-next dependency-version: 16.3.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: airlock-npm ... Signed-off-by: dependabot[bot] <support@github.com>
Author
|
This pull request was built based on a group rule. Closing it will not ignore any of these versions in future pull requests. To ignore these dependencies, configure ignore rules in dependabot.yml |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps the airlock-npm group with 14 updates in the / directory:
1.62.11.63.02.9.02.9.14.4.34.6.524.13.324.13.50.1.30.2.00.2.40.3.016.3.316.3.519.2.819.3.019.2.1819.3.019.2.819.3.019.2.519.3.03.3.63.3.719.2.1819.3.019.2.519.3.09.39.510.11.016.3.316.3.5Updates
playwright-corefrom 1.62.1 to 1.63.0Release notes
Sourced from playwright-core's releases.
... (truncated)
Commits
1b025d7chore: mark v1.63.0 (#42569)0b9956dcherry-pick(#42568): docs(test): mark test.step subtitle option as since v1.6313dbf10cherry-pick(#42552): docs: release notes for v1.63e93b64echerry-pick(#42566): feat(test): add subtitle option to test.step (#42567)2b7a5f2test: response.body() for content-encoding:identity (#42537)648a67cfix(mcp): create parent directories for explicitly named files (#42540)7894f56docs(mcp): clarify how tool file names are resolved (#42538)52900a1devops: restore npm publishing from GitHub Actions (#42550)8c47f59docs(csharp): fix nonexistent method names in guide examples (#42507)bd6e552chore(video): emit frames with real timestamps, drop frame number quantizatio...Updates
yamlfrom 2.9.0 to 2.9.1Release notes
Sourced from yaml's releases.
Commits
1440ecd2.9.1c699bc5fix: Simplify line unfolding during quoted string parsing (#714)d11ce77fix: Limit recursive merge aliases (#713)c5f49f4chore: Update docs-slateUpdates
zodfrom 4.4.3 to 4.6.5Release notes
Sourced from zod's releases.
... (truncated)
Commits
59bbc03chore: re-pin the integration peers to the workspace zod after the 4.6.5 bump0f3f5ee4.6.5cc4cd4eRevert "Revert "feat: add z.currencyCode() over a vendored ISO 4217 list, ref...ca0229aRevert "feat: add z.currencyCode() over a vendored ISO 4217 list, refreshed w...56222cdfeat(instanceof): key the .properties() shape off the instance type (#6600)de65a5cdocs: lead the properties section with the check and add a Zod Mini tab (#6598)f1448f7docs: fold the 4.6.x patch highlights into the 4.6 post's own sectionsd2b135cdocs: add the 4.6.x patch highlights to the 4.6 post2bb0871chore: re-pin the integration peers to the workspace zod after the 4.6.4 bump743aedb4.6.4Updates
@types/nodefrom 24.13.3 to 24.13.5Commits
Updates
@truefoundry/trueforge-sdkfrom 0.1.3 to 0.2.0Release notes
Sourced from @truefoundry/trueforge-sdk's releases.
... (truncated)
Changelog
Sourced from @truefoundry/trueforge-sdk's changelog.
... (truncated)
Commits
8b1d98eVersion Packages (#801)5ae0781Rewrite changelog to tag truefoundry specific items208ecbaVersion Packages (rc) (#783)551b6a8feat: configure MCP tool approval in Build Agent (#770)bd67d56feat: Add a event schema with paused/running status (#785)e4c4b56feat: introduce built-in web search capabilities in TrueFoundry (#761)4a91f47feat: Allow renaming sessions (#780)584e815AGE-2217: Expose tenant CREATE via list-permissions (#750)7fc322fVersion Packages (rc) (#738)9846d6dfeat: add Python samples to agent Use in Code (#736)Updates
@truefoundry/trueforge-uifrom 0.2.4 to 0.3.0Release notes
Sourced from @truefoundry/trueforge-ui's releases.
... (truncated)
Changelog
Sourced from @truefoundry/trueforge-ui's changelog.
... (truncated)
Commits
8b1d98eVersion Packages (#801)d29717bAuto prepend Bearer during MCP connection with API Key and add hints51127c4Enable agent Clone via Save drawer for create-capable viewers (#794)a273ed8chore: Preload Monaco editor on UI shell mount for improved performance (#791)5ae0781Rewrite changelog to tag truefoundry specific items208ecbaVersion Packages (rc) (#783)ffbb239feat: add delete confirmation dialog to Sessions list with hover menu (#743)551b6a8feat: configure MCP tool approval in Build Agent (#770)bd67d56feat: Add a event schema with paused/running status (#785)4be60e7fix: bump assistant-ui-runtime to 0.1.41 (#784)Updates
nextfrom 16.3.3 to 16.3.5Release notes
Sourced from next's releases.
Commits
ca2c75ev16.3.514fb290[backport] Fix use cache prerender signal retention (#98448)2b1f28d[16.3.x] Add CSP nonce to script tags of loading and template files (#98403)4b56cee[16.3.x] Backport docs fixes (#98317)5568a02[backport] docs: local development: Rewrite docker section, add Windows Dev D...93249ab[16.3.X] Emit whole-app server NFTs whenoutput: 'standalone'is used with ...6549fd7[16.3.x] next/image: reject empty image on read/write to disk cache (#98186)d9eac96[16.3.x] next/image: skip 0-byte entries when initializing disk LRU cache (#9...84b35fe[test] Fix 16.3 deploy test assertions (#98133)14f9c1a[16.3.x][ci] Run flake detection and new deploy tests when merged and on back...Updates
reactfrom 19.2.8 to 19.3.0Release notes
Sourced from react's releases.