carlos / platform Public

worktree-store-scan-on-demand

Scan on demand: an app asks for one object to be scanned, reads the verdict back, and optionally gets a signed webhook when it is done. Stacked on worktree-store-virus-scanning, which is already merged.

The sweep half answers "is this object clean?" within the hour. For an upload that is useless — a user waits, or the app serves bytes nothing has looked at. This is the other half.

What is here

Tasks 1–17 of docs/superpowers/plans/2026-09-02-scan-on-demand.md. The design is docs/superpowers/specs/2026-09-02-scan-on-demand-design.md (draft 7, seven review rounds).

  • POST .../store/scan/object — enqueue, role Service, behind the consent chain
  • GET .../store/scan/object?key=&wait= — the verdict, with a long-poll
  • a drain goroutine on its own 30s clock, with a boot rebuild
  • signed callbacks to the app's platform host, delivered by a bounded worker pool
  • per-app callback keys, deployment and per-app rotation, metering

The two properties worth reviewing

The console must never read a tenant's bytes without consent. Provisioned, not trashed, member requested, not attested client-encrypted, operator granted, deployment switch on. One function, objstore.QueuePermitted, called by both doors and by the drain — and the drain re-checks per object, not per page. A review found it checking once per page and then scanning up to fifty objects under that one answer, so a revoked grant or a re-attestation was honoured only from the next tick.

A callback must not claim what it cannot justify. The final review found the drain stamping every outcome with the engine and signature database this tick is configured with, before knowing whether the engine had run — so a verdict read from a week-old tag was POSTed, signed, as though today's engine had produced it. The status door already refused to make that guess; the two surfaces disagreed and the signed one was lying. Provenance is now filled in only where the engine actually ran.

Reviews, and what they cost

Three rounds, and every one found something the tests had not:

| | | |---|---| | consent checked once per page, not per object | up to 49 more objects read under withdrawn consent | | the path validator had a second door | /%5c%5cevil/x and /%2f%2fevil/x — encoded spellings of shapes it already refused | | drainOne swallowed a refusal it had just observed | items deleted that §7.5 says to keep, and a verdict with no result object behind it | | webhook_gen did not prevent what it claimed | PurgeApp deletes the record, so a reclaimed app name derived the same key | | signed callbacks claimed engine provenance | a signature database that never judged the object |

The fourth was found by the implementing model reading its own instructions and disagreeing with them, which is why it was asked to.

Two things a reviewer should know

The tests were the weak part, not the code. Several asserted the shape of a result rather than the property in their name — TestDeliveryDoesNotBlockTheTick never ran a tick; TestNeverPostsUnsigned returned before the signer was consulted; TestCallbackBodyCarriesRecorded marshalled its own literal. Each is rewritten to fail when its behaviour is removed, and the main paths are mutation-tested.

The wire format is frozen by TestCallbackWireFormatGoldenVector. The docs hand those bytes to app authors to reimplement in other languages, and every tenant holds a key derived by that function; change the derivation and every callback everywhere stops verifying at once. A deliberate v2 adds a signature alongside v1 rather than editing the constants.

Not merged-ready without one thing

The tenant-facing documentation is not written, and the final review argues it should land with the merge rather than after. The moment this merges, the enqueue door accepts callback_path from any Service credential and the console will POST signed bodies to apps that have no documented way to verify them. That page needs VerifyCallback and must say that the delivered secret is hex while the MAC is keyed on the raw bytes — an author who feeds the environment string straight into HMAC fails every verification, and the next move after that is to stop verifying.

Drafted copy is held out of these commits pending review; the plan's tail lists it, along with what is known-and-unfixed (no at on a tag verdict, no reason on skipped, skipped bytes metered, consent checked once per long-poll).

🤖 Generated with Claude Code

Commits 16
Files changed 38 +6134 −123
Discussions 0 0 open
Tasks 0/0 done

Discussions

  • No discussions on this branch yet.

Tasks

  • No tasks yet.

Activity