7 Commits

Author SHA1 Message Date
Michał Pierzchała 0da105e3c3 docs: simplify agent context and resolve conflicting guidance (#2287)
* docs: clarify agent task scope and validation

* docs: remove redundant and conflicting agent guidance
2026-09-05 20:04:40 +02:00
Michał Pierzchała e882cf9723 feat(runtime): add managed-local ownership and the exact-only managed runtime (#2258)
* docs: trim the CONTEXT.md glossary within the guidance byte budget

CONTEXT.md sat at 11,992 of its 12,000-byte guidance budget, so no new domain term could be added
without first paying for it.

- Condense eighteen definitions that had grown past one line (platform leaf, command surface,
  runtime use, runner command traits, interactor, coordinate-first resolved element activation,
  parent-owned touch point, guarantee cell, delegation-on-error, ref frame, snapshot producer,
  snapshot policy facet, capture hint, regular presented-depth frontier, clip fold,
  AX-unavailable target invalidation, Maestro program, Maestro observation generation). The
  definitions keep their meaning; only the elaboration is gone.
- Move the five test-harness terms of 'Providers and tests' (provider-backed integration
  scenario, provider transcript, scenario transcript, in-process provider scenario harness, HTTP
  contract test) to docs/agents/domain.md, which AGENTS.md already routes to for domain
  vocabulary. None of them names a concept a command or a wire shape carries, and none appears in
  a test name.

CONTEXT.md is 10,517 bytes after this pass.

* feat(runtime): add the managed-local owner kind, device-claim rule, and managed binding fence

ADR 0021 foundations, unit 1. Nothing registers a managed local owner yet, so every arm below is
reached from tests only; the point of the unit is that the arms exist and fail closed.

- `RuntimeOwnerRef` gains `{ kind: 'managed-local'; instance }` with `managedLocalRuntimeOwner`:
  one owner per allocator instance, family-agnostic because the device carries its family. Every
  owner-kind discrimination becomes an exhaustive switch, so a fourth kind is a type error at each
  site: the owner key, the unavailable-facts provider mode, the durable envelope decode, and the
  gateway's provider-mode acceptance and exact-owner selection.
- `deviceClaimRuleForOwner` ('ordinary' | 'allocator-held' | 'none') in the new leaf
  src/daemon/device-claim-rule.ts replaces the boolean `isLocalDeviceClaimTarget`. Both claim
  gates switch on it, and the admission gate now evaluates it under every device-claim policy: the
  `transient-exclusive` condition moved inside the ordinary arm, so a managed owner is verified
  where an ordinary owner would never have touched the store.
- `requireAllocatorHeldDeviceClaim` (src/daemon/device-claim-allocator.ts) is the one read-only
  verifier both gates consult. It never acquires, never locks and never clears; in this unit it
  can only answer `binding-invalid`, `missing`, or `conflict`, because no allocator-held claim
  kind exists until unit 2. `allocatorHeldAdmissionError` answers each outcome with its own
  refusal through an exhaustive switch, so an outcome the verifier learns to produce is a
  compile error until it is answered.
- A missing allocator-held claim refuses with COMMAND_FAILED / `allocator-claim-missing`,
  `retriable: false`. It is deliberately not a `DeviceClaimConflictReason`: replay retries every
  conflict reason as infrastructure, and a managed identity no allocator activated is permanent.
- `managedBindingFence` / `decodeManagedBindingFence` encode `[requesterId, identityIncarnationId]`
  as the fence token and the request generation as its generation, so two requesters on one
  identity incarnation never share a fence. The ids are fenced verbatim, and the decoder accepts a
  token only if it re-encodes to itself.
- Claim admission now receives the binding intent the gateway bound, so an exact-owner fence
  reaches the gate unchanged. Session open still binds ordinarily and passes an ordinary intent:
  a managed local owner is therefore refused there structurally, and the Host open route replaces
  that intent when it lands.
- CONTEXT.md: managed local owner, device-claim rule, managed binding fence, request generation,
  identity incarnation.

* fix(daemon): decide allocator-held admission totally instead of by an optional error

`allocatorHeldAdmissionError` returned `AppError | undefined`, so its switch without a default
was never exhaustiveness-checked: TS2366 fires only when the return type excludes `undefined`,
`noImplicitReturns` is off, and oxlint has no exhaustiveness rule. A verifier outcome nobody
answered would therefore fall out as `undefined`, which both gates read as an admission — claim
admission throws nothing and session open proceeds to open the session on a device it never
verified.

Replace it with `decideAllocatorHeldAdmission`, returning
`{ admitted: true } | { admitted: false; error }`. The return type excludes `undefined`, so
dropping an arm is now a compile error at the switch, and a gate asks whether the outcome was
admitted rather than whether an error happened to come back. `buildAllocatorHeldRefusal` and the
admission gate are projections of that one decision.

* docs: restore the meaning five CONTEXT.md definitions lost in the trim

The condensing pass shortened these five past the point where they still said what they meant:

- Capture hint said 'presented depth' where the term is 'regular presented depth', which is what
  Regular presented-depth frontier is measured against; the short form read as a different axis.
- Clip fold lost both that the interpreter runs inside presentation for every backend and that a
  platform difference may not enter as a backend exception. Those are the whole rule.
- Snapshot policy facet lost the process boundary that makes it host-side at all: runner-side
  Swift presentation stays separate.
- Runner command traits lost 'independently of the public command surface', which is what
  distinguishes them from the command surface.
- Delegation-on-error said 'settles', and Settled observation makes 'settle' a term of its own.

CONTEXT.md is 11,674 of its 12,000-byte budget.

* docs(daemon): correct the claim-gate and managed-owner comments

- The claim-gate docstring claimed there is no other way to obtain device operations. That is
  true of command handlers, but two daemon-owned recovery paths bind outside the seam:
  application-lifecycle-recovery.ts (ordinary intent, daemon shutdown) and
  durable-capture-runtime-recovery.ts (exact-owner intent read back from a durable envelope,
  which this unit makes able to carry a managed local owner). Name them instead of claiming
  coverage the seam does not have.
- The open path's comment described a session executing under an allocator-held claim, a state
  this route cannot produce. Say what the `{ kind: 'ordinary' }` literal actually is: the truth
  of a route that binds ordinarily, which the Host open route replaces with the request's exact
  intent when it lands.
- Name U3 as the unit that fills the exact-owner selection arm, rather than the whole ADR.

* fix(runtime): accept transport-composed facts for a managed owner

providerModeMatchesOwner's managed-local arm accepted mode === 'local' only, but
selectExactOwner's managed-local arm loads the device's local family owner through the same
loadLocal a local-family owner uses, so it inherits that owner's provider modes verbatim. A
managed binding over a transport-composed local device (e.g. a remote ADB or web-provider
transport) would fail bindingContractFailure's facts check and be rejected as an owner/facts
mismatch. Accept the same local-family modes the local-family arm already does; still
unreachable until U3 registers the exact-only owner, which is where the binding regression
test that pins this lives.

* feat(runtime): register the managed local owner as an exact-only wrapper and add the neutral allocator port (#2259)

* feat(runtime): register the managed local owner as an exact-only wrapper and add the neutral allocator port

ADR 0021 foundations, unit 3. Unit 1 added the `managed-local` owner kind and left the gateway's
exact-owner arm for it failing closed; this unit gives that arm a registry and the owner it selects.
Nothing in production registers a managed owner yet, so both are reached from tests only.

- `createComposedPlatformRuntimeGateway` gains a `managedOwners` list that only the `managed-local`
  arm of `selectExactOwner` reads. `selectOrdinaryProvider`, `inspectFacts` and the ordinary `bind`
  arm never see it, and `providerModules` pairs one provider-runtime owner with one
  `ProviderDeviceRuntime`, so ordinary selection cannot reach a managed owner by construction
  rather than by a check. A duplicate instance is refused at composition.

- The wrapper (src/platform-runtime-managed-owner.ts, root zone, no platform imports) binds only
  under an exact-owner intent naming itself, loads the device's own family owner through the
  gateway's loader, delegates with an ordinary intent — a family owner refuses a foreign exact
  owner — and republishes the binding under the managed owner. It does not read the fence: what a
  managed binding fence proves is the device-claim gate's business. `ownsDevice` returns false.

- Twenty cells are withheld as `owner-capability-missing`, enumerated by mechanics rather than by
  catalog group: the four device-lifecycle cells, the four application cells that boot or shut the
  device down (`prepareApplicationOpen`, `prepareAppleRunner`, `closeApplication`,
  `finalizeApplicationClose`), and the twelve durable-capture cells, which a managed binding could
  never reattach because the family runtime stamps envelopes with its own local owner. The
  operations are then filtered by those facts, so an operation cannot outlive its own fact.

- `@agent-device/contracts/managed-device-allocation` is agent-device's own allocator port: lease
  request, lookup, supersession, cancellation, renewal, release, activation confirmation, identity
  status, removal acknowledgement, and the typed environment projection. Types only, named to match
  the allocator's published contract so the two sides cannot drift, with no dependency on any
  allocator package. Its only implementation is a scripted fake under `*.fixtures.ts`.

- Budgets: the new contracts entry surface is a one-module closure; the `src/platform-runtime.ts`
  hub moves 47 -> 48 for the wrapper, whose own value imports were already in that closure.

* fix(runtime): withhold the deployment cells from a managed binding and trim the allocator port

Review findings on the managed local owner.

- `deployApp` and `deployMaterializedApp` join the lifecycle group. Both family deployment runtimes
  ensure device readiness before installing, and `deployAppUse` requires `deployApp` alone — so
  `install` on a managed binding would have booted the allocator's device with nothing to refuse
  it. Twenty withheld cells become twenty-two, and the refused-uses test covers `deployAppUse`.

- The wrapper's doc comment no longer implies that withholding cells is a complete lifecycle
  exclusion: several retained Apple cells (screenshot capture, settings, clipboard, application
  launch) boot the simulator lazily inside the family runtime, where cell selection cannot reach.
  That is the same class as the pre-binding readiness path, and closing it is a family-runtime
  change.

- `readLeaseEnvironment` leaves the allocator port. It was beyond the vocabulary the contract
  fixes, and it made the scripted fake carry a real parser whose only test passed with every
  production line reverted. `ManagedLeaseEnvironment`, `ManagedLeaseEnvironmentKey` and
  `LeaseEnvironmentError` stay as types; the reader that produces them lands with the unit that
  first turns a grant into a device.

- CONTEXT.md drops an operation enumeration that was already incomplete.

* fix(runtime): withhold the lazily-booting Apple system and screenshot cells

Screenshot capture, settings, clipboard and application launch were retained on a managed
binding even though their Apple family-runtime implementations can boot the simulator lazily
below cell-selection granularity (screenshot's shutdown-failure retry boot; settings, clipboard
and application launch each resolve a local interactor the same way). That preserves rather than
blocks the exact bypass ADR-0021 section 3's hard boundary names: managed lifecycle/readiness
belongs to the allocator, and no handler path may fall back to direct lifecycle tooling.

Withhold captureScreenshot, setSetting, readClipboard, writeClipboard and openApplication
alongside the existing withheld groups. The wrapper's doc comment now names the pre-binding
readiness gap explicitly as the same class of follow-up, rather than folding it into a retained-
cells caveat that no longer applies. MANAGED_RETAINED_OPERATION moves to tapPoint, the cell the
fixture-based regression tests now use to prove something survives the wrapper.

* chore: retrigger CI (stale synchronize event after rebase)

* fix(runtime): lazy-load the managed owner wrapper to satisfy the eager-closure no-growth gate

Main's eager-closure budget gate (the merge-base ratchet) replaced the hand-tracked
HUB_BUDGETS map with an automatic no-growth-vs-merge-base check: src/platform-runtime.ts
is a hub with no growth allowed at all, not a number bumped by hand with a justifying
comment. The static import of createManagedLocalRuntimeOwner in platform-runtime-gateway.ts
added one module to that hub's closure (47 -> 48), which now fails
scripts/__tests__/eager-closure-budgets.test.ts outright rather than needing a manual bump.

Move the value import into loadManaged's dynamic `await import`, matching how the rest of
this file's owner loaders defer their leaf modules. Only the managed-local arm reaches this
path, so an ordinary bind never pays for it, same as before -- the wrapper module itself was
simply the wrong side of the eager/lazy line.
2026-09-03 19:41:53 +02:00
Michał Pierzchała 40e4b0dd3e docs(agents): restore and enforce progressive disclosure (#1888)
* docs(agents): restore and enforce progressive disclosure

* test(maestro): pin typed selector fallback signal

* docs(agents): address progressive disclosure review

* docs(agents): restore orphaned traps and close guidance-gate bypasses

- AGENTS.md: skills carry a minimal start/routing card; command semantics
  stay in versioned CLI help (the skills contract enumerates two skills by
  hand, so prose retains ownership for the rest)
- testing.md: restore the two local-only XCTest snags CI never hits
  (unsigned-bundle policy refusal signature + first-run automation permission)
- scripts/gate/routing.ts: record GitHub's 300-changed-file path-filter limit
  at the paths-ignore assertion it bounds
- agent-guidance-contract.test.ts: recurse docs/agents so nested guidance
  cannot evade the byte budgets while the gate stays green
2026-08-20 16:58:26 +02:00
Michał Pierzchała 1a76344685 docs: restructure AGENTS.md and CONTEXT.md for progressive disclosure (#1402)
* docs: restructure AGENTS.md and CONTEXT.md for progressive disclosure

Apply the Claude 5 context-engineering guidance to the repo's agent docs:
keep the always-loaded file to gotchas and invariants, and move situational
guidance one hop away behind a routing table.

AGENTS.md 315 -> 229 lines. Cut generic agent-behavior boilerplate, three-way
duplication (Common Mistakes restated Hard Rules; Finding Source Owners
restated the registry section), and facts visible from the repo itself.
Kept verbatim: the expensive-lessons principles, enforcement gates, Hard
Rules, and environment traps.

Split out docs/agents/{cli-flags,pull-requests,device-verification}.md and
folded the Testing Matrix into docs/agents/testing.md, reframed around
pnpm check:affected so the prose stops duplicating the selector.

CONTEXT.md keeps all 50 terms, now grouped under a section index so a task
loads one section instead of the whole glossary.

* fix(check-affected): move the selector-owning sentinel to the Testing Matrix

The Testing Matrix moved from AGENTS.md to docs/agents/testing.md, but the
affected-check selector still treated only AGENTS.md as selector-owning. A
later matrix edit would have been classified as inert docs and skipped the
fail-open, so the selector could keep deriving gates from a spec that had
changed underneath it.

Move the sentinel with the prose, as a named SELECTOR_OWNING_DOCS set so the
next move is one line, and fix the two in-code comments plus the testing.md
paragraph that still pointed at the AGENTS.md matrix.

* docs: restore two rules dropped by the AGENTS.md split

Review caught two repo-specific rules that did not survive the move. Both are
prose without any backticked identifier, so the identifier-diff used to verify
the split could not see them.

- "Test through public interfaces; do not add unrelated production exports
  solely to enable tests" returns next to the behavioral-tests rule in
  docs/agents/testing.md, with the reason it exists.
- The guidance-ownership rule (decide whether new guidance/schema/metadata
  belongs to the command surface, CLI grammar, CLI help, MCP projection, or
  daemon runtime) returns to the always-loaded Docs & skills section, since it
  governs all command-surface work and not just the flag case.

Also point the ADR routing row at docs/adr/README.md, which is already the
"read when you touch…" index, rather than at the bare directory.
2026-07-25 12:13:09 +02:00
Michał Pierzchała c6fd3dc972 test: add live web platform smoke (#832)
* test: add live web platform smoke

* test: harden web smoke cleanup
2026-06-21 13:05:51 +02:00
Michał Pierzchała d05cfc727b feat: manage web backend setup (#833) 2026-06-20 11:44:00 +02:00
Michał Pierzchała 59d28e8446 refactor: add provider-first device lab tests (#542)
* refactor: add provider-first device lab tests

* refactor: tighten device lab provider seams

* test: cover provider lab contracts

* docs: record device lab harness direction

* ci: run device lab integration tests

* test: move device lab under integration

* test: extract device lab helpers

* refactor: centralize apps filter defaults

* test: drop lab-covered unit tests

* test: fold platform happy paths into device lab

* test: reuse device lab helpers

* test: move device lab to in-process harness

* test: replace session handler cases with device lab

* test: harden device lab scenario contracts

* docs: define unit test retention policy

* test: expand provider device lab coverage

* test: harden provider device lab coverage

* test: cover manifest install and runner session contracts

* chore: remove unused provider cleanup code

* test: split android find device lab scenario

* test: track provider lab architecture progress

* test: clarify provider lab roadmap progress

* test: advance provider lab session coverage

* test: move menubar click routing to device lab

* test: move menubar snapshots to device lab

* refactor: centralize screenshot flag plumbing

* refactor: colocate screenshot flag metadata

* test: cover all public commands in device lab

* test: move macos wait success to device lab

* test: drop redundant perf and diff units

* test: move push payload paths to device lab

* test: move network parsing to device lab

* test: move log cleanup to device lab

* test: move log restart and boot to device lab

* test: move ios physical boot to device lab

* test: cover perf startup in device lab

* test: extract android and ios device lab worlds

* test: trim device lab world surface

* test: split snapshot capture unit coverage

* test: deepen device lab coverage and trim handler units

* test: clean up device lab migration scaffolding

* test: report device lab public command coverage

* refactor: make Apple provider seams semantic

* refactor: tighten device inventory and Linux provider seams

* refactor: tighten request provider scoping

* refactor: add semantic macos host provider

* test: broaden device lab find coverage

* test: cover workflow flags in device lab

* refactor: promote linux input provider seam

* test: clarify device lab flag coverage

* test: classify snapshot force-full progress

* test: enforce device lab progress in ci

* test: stabilize device lab ci

* test: move packaged metro smoke to integration

* test: drop stale provider seam coverage

* test: harden provider scope regression coverage

* refactor: remove stale platform barrels

* refactor: keep linux clipboard and screenshots semantic

* refactor: move macos host tools behind provider

* fix: honor remote artifact output paths

* test: deepen runtime coverage for daemon and runner paths

* test: share loopback test helpers

* refactor: make daemon runtime importable

* fix: honor replay target metadata

* chore: tighten final device lab quality gates

* test: share device lab setup helpers

* test: remove generic apple lab fallback

* test: deduplicate device lab helpers

* chore: tighten fallow duplication signal

* refactor: share apple diagnostic helpers

* fix: detect active android ime during fill verification

* test: consolidate provider-backed integration suite

* ci: fix fallow and iOS smoke setup

* chore: consolidate cleanup after ci fixes

* test: split vitest unit and integration projects

* docs: mention MCP discovery metadata

* docs: add agent skills context pointers

* fix: close provider recording coverage gaps

* fix: restore mcp compatibility smoke

* test: cover provider edge regressions

* test: consolidate loopback helpers

* docs: remove stale provider routing reference

* fix: harden final provider review issues

* chore: defer mcp cleanup from provider refactor
2026-05-18 14:50:52 +02:00