Skip to content

feat(enrollment): simlock device add / remove enroll and unenroll physical devices - #449

Merged
V3RON merged 22 commits into
mainfrom
task/437
Oct 11, 2026
Merged

V3RON merged 22 commits into
mainfrom
task/437

Conversation

@V3RON

@V3RON V3RON commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Closes #437

Status

Implement: done (31/31 green) Review: 5 rounds, none open Mutate: 8 alive, all equivalent Hardware: n/a Gate: not run
Next: gate

Done when

  • pnpm test:e2e e2e/physical-enrollment.test.ts passes: with the fake driver, device add enrolls a scripted iPhone, status --json shows it ready and physical, and one device.enrolled is recorded.
  • With the fake driver, each scripted refusal answers DEVICE_NOT_ENROLLABLE with its reason, and no record is written.
  • An agent session's device add and device remove, over the CLI and over HTTP, answer FORBIDDEN, and the driver is never asked.
  • device remove of a ready device leaves it deleted, records device.deleted with initiator: "operator" and physical: true, and the driver log shows no call for it.
  • An alias of an enrolled device re-enrolls the same record; list --devices never shows two non-deleted records with one canonical ID.
  • On a gateway, device add and device remove answer UNSUPPORTED_IN_GATEWAY_MODE, with a message to run them on the worker, and no worker is asked.
  • PROTOCOL_VERSION_RANGE is one above main's when this PR merges.
  • simlock list --devices on a gateway, and simlock status --json from an agent session, show an enrolled device's canonical ID as driverDeviceId.
  • The console's worker page marks a physical device "Physical".
  • Both EVENTS.md files list device.enrolled, and device.deleted with operator and physical.
  • docs/CLI.md, docs/HTTP-API.md and docs/CLIENT.md document both commands, both routes and both methods.

Assumptions

  • Assumption: what device.remove answers as id — the registry ID, as device.add and status use id; driverDeviceId carries the canonical ID.
  • Assumption: which Simlock simulator or emulator makes a virtual-device refusal — any non-deleted virtual record on either platform whose driverDeviceId or address is the typed ID, the narrowest reading of "Simlock's own".
  • Assumption: how a virtual device's driverDeviceId stays off a status entry now the schema has the field — statusDeviceSchema drops it for a non-physical device on parse, because a gateway's worker view parses full list.get records through it; this keeps the old output for every virtual device.
  • Assumption: which check comes first in add when a record exists — enrolled on the other platform, then leased, then reclaiming or a held reclaim claim, then another claim (busy), as the spec's step 4 lists them.
  • Assumption: the wording of the DEVICE_BUSY and UNKNOWN_DEVICE messages and of the gateway refusal — each names the ID and what to do next, the plainest text that carries the required fact.

Equivalent mutants

  • src/contract/errors.ts, the true flags of DEVICE_NOT_ENROLLABLE, DEVICE_BUSY and UNKNOWN_DEVICE in the details-declared map: type-level key set, nothing reads the value.
  • src/core/physical-enrollment.ts, the claim === undefined busy refusal in add: #requireFree and tryClaim run in one synchronous step under decisions.run, so it cannot be reached.
  • src/core/registry.ts, error instanceof IllegalTransition in unenrollPhysical and in reenrollPhysical: transition throws nothing else.
  • src/daemon/server.ts, the options.enrollment === undefined spread: an undefined property reads the same as an absent one in the dispatcher.

How each Done when line was checked

Lines 1-6 and 8 by e2e flows in e2e/physical-enrollment.test.ts on the out-of-process fake driver (and e2e/gateway-* for the gateway lines); line 7 by the PROTOCOL_VERSION_UNSUPPORTED e2e test and the version diff; line 9 by e2e/console/workers.spec.ts; lines 10-11 by the claims review of both EVENTS.md files and the three docs.

Review

Spec review: 21 blocking, 11 fixed, 7 notes. Code review: 22 blocking, 6 fixed, 8 notes. Claims review: 15 blocking, 54 fixed, 8 notes. Comment review: 155 blocking, 155 fixed, 0 notes.
Mutate: 527 mutants, 8 alive.

Comment-review findings were fixed through the claims review's deletion lines. The last two claims lines (docs/CLI.md:1809, docs/internal/COMPONENTS.md:65) were fixed in 37b9a6a and not reviewed again.

Rejected:

  • spec: src/core/physical-enrollment.test.ts:305 no test proves INTERNAL for a throwing inspection — titles now match bodies; dispatcher.test.ts:3392 proves pass-through
  • spec: src/contract/protocol.ts:16 the comment no longer names device.add as the reason for the bump — rule 17 forbids it; Done when 7 was changed
  • code: src/core/quarantine-coordinator.ts:219 claim held while device.deleted fires — not blocking, a deleted device is never granted
  • code: src/core/physical-enrollment.ts:190 the allowlist repeats the state machine — not blocking, nothing else can reach it today
  • code: e2e/physical-enrollment.test.ts:295 title claims more than the body proves — not blocking, it is the spec's Tests line word for word
  • comments: e2e/fake-driver/types.ts:1-6 header breaks rule 17 — not blocking, it gives the why
  • comments: src/daemon/dispatcher.ts:124 test convenience as the reason — rule 17 allows a why for an optional dependency

Written by an agent.

V3RON added 21 commits October 11, 2026 09:47
…sical devices (#437)

30 failing: every flow of e2e/physical-enrollment.test.ts fails on the
unknown command `device`. The scripted-device script types are added
beside it so the e2e typecheck holds.
…evices (#437)

30 failing -> implementation in place; unit tests follow.
…ical in the fixtures (#437)

9 failing -> 0 failing
…ete comments that say what; docs that named the old facts (#437)

0 failing -> 0 failing
…, dispatch, routes, client (#437)

174 alive -> unit tests added for each; one guard left as equivalent
21 alive -> re-run without the branch's cached mutant results
…hrough transition; tests prove their titles; docs name the busy operations (#437)
…comments and docs true of the code (#437)

Review findings: 2 test titles, 1 duplicated decision, 12 claims.
…437)

Spec test added after review r2; 1 missing -> 0 missing
@V3RON

V3RON commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

Review notes

Not blocking, not verified. Each is one reviewer's claim.

  • code: src/core/physical-enrollment.ts:131 the virtual-device check matches only the typed ID; an alias whose canonical read equals a managed simulator's driverDeviceId gets enrolled
  • code: src/core/physical-enrollment.ts:190 the ready-or-quarantined allowlist answers busy to every other state; the task that adds absent must change it, and the state machine's legal sources for deleted in the same step
  • claims: src/contract/errors.ts:344 "as NO_CAPACITY does: 409 and 11" reads as if NO_CAPACITY were 409; it is 503
  • claims: src/daemon/error-code.ts:11 "a new core error means one branch here", but the enrollment errors are mapped in dispatcher.ts
  • spec: src/leasing/create-leasing.test.ts:4147 the retitle drops the spec's claim that device.quarantine-recovered starts the pool pass; the body shows the event and ready 1, not that the event started the pass
  • spec: DEVICE_BUSY details carry id as the canonical ID while DEVICE_NOT_ENROLLABLE details from the same device.add carry id as typed
  • spec: src/cli/index.test.ts:5361 test fakes still return device.remove id equal to the canonical ID
  • code: src/core/quarantine-coordinator.ts:53 the deleted class comment held why give-up destroys and never shuts down (a shutdown device is reusable warm stock)
  • code: src/core/driver.test.ts:245 the runtime assertions cannot fail; only typecheck proves the title
  • code: e2e/physical-enrollment.test.ts:295 the title says the client "never sends device.add", but the test never sends it; the clause fits a new client and an old daemon, the body tests an old client against a new daemon

Written by an agent.

V3RON added a commit that referenced this pull request Oct 11, 2026
…iew; fewer pushes; Stryker failures say why (#452)

From the #437 delivery (PR #449). Each change targets a class of
problem, not that run's particular defects.

- **CI failures reach fix runs.** Every push cancels the CI run before
it, so the head's checks were always pending at the pre-round check. A
real e2e failure stayed red through 4 review rounds unseen. The new
`.agents/scripts/ci-failures.mjs <pr>` prints the failed tests of the
newest finished run of each workflow on the branch. `deliver` runs it
before every review round and fix run, and puts its failures into the
fix run's `fixes`. Implementers use it for their `ci:` value.
- **Audit runs the comment review.** The `comment-reviewer` brief now
lives in its own definition, so the reviewer and the implementer's audit
spawn it the same way. Audit deletes what it flags instead of rewording.
The claims review now judges only whether a comment is true. Whether a
comment should exist is the comment review's call.
- **Rewritten docs and comments cite the code.** In fix mode, each fact
in new text names the `path:line` that shows it. Fixes that brought in
new false claims cost rounds in #436 and #437.
- **Fewer pushes.** Each push runs mutation testing for minutes.
Implementers now push when they need the mutant list, and once before
the report. They read the whole push output.
- **Stryker failures say why.** `mutate.mjs` prints the last lines of
Stryker's error output at the end and exits 2. Before, it exited 1, and
the hook then printed "Mutants survived". A crash on #449 couldn't be
reproduced at the same commit, and its cause was lost to `| tail`.

Tests: `src/ci-failures.test.ts` (new) and `src/mutate.test.ts` (one new
case). Every check was probed by breaking the script.
@V3RON
V3RON merged commit 8eb9c87 into main Oct 11, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

simlock device add / remove: an admin enrolls and unenrolls physical devices

1 participant