Context
#364 deliberately holds/reverts #360 from the next engine release. #360 intended to preserve tags set by trusted workspace-key enrollment across later bare node.register, but it merged with unresolved correctness and authority findings.
The machine enrollment work in #363 does not close them. It adds serializeMachineEnroll(workspaceId, machineId) under a ['machine', workspaceId, machineId] key, while node-control registration uses serializeNodeOp(workspaceId, nodeId) under a deliberately separate ['node', workspaceId, nodeId] key. Enrollment without machine_id takes no machine lock. Thus createNodeToken and registerNode can still interleave, and the process-local queues also do not order separate Cloudflare isolates.
Required behavior
Restore the useful part of #360 without inferring authority from a string prefix:
- Tags written by authenticated workspace-key enrollment remain present across a later registration that omits them.
- A node-supplied legacy
cloud:* string does not become trusted or permanently preserved merely because it was already persisted.
- Do not reserve the entire
cloud:* namespace; documented non-repo: client tags must retain their existing round-trip/update behavior.
- Concurrent enrollment and registration cannot erase a newly enrolled tag or resurrect a cleared tag through a stale snapshot.
Design constraints
- Store explicit tag ownership/provenance (for example a dedicated enrollment-owned tag field/table); do not reconstruct authority from
nodes.tags prefixes.
- Enrollment mutation and node registration must share ordering for the resolved node identity. The guarantee must survive the hosted Cloudflare topology: adding another process-local map key is insufficient across a worker/NodeDO isolate boundary.
- If machine-keyed enrollment also takes a node-identity lock, define and document one acyclic lock order; never acquire machine and node queues in opposite orders.
- Keep HTTP JSON fields snake_case and validation in zod.
- Update
README.md and openapi.yaml together for any public tag contract change.
Discriminating controls
Use deterministic interleaving tests, not SQL/string assertions:
- Registration reads the old row, enrollment adds an enrollment-owned tag, registration resumes: the tag survives.
- Registration reads the old row, enrollment clears an enrollment-owned tag, registration resumes: the tag is not resurrected.
- A client-supplied legacy
cloud:* tag is not promoted to enrollment-owned authority.
- A non-authoritative non-
repo: cloud:* client tag can still be updated/removed by registration.
- Positive control: trusted enrollment-owned placement tags survive a bare re-registration.
- Hosted-runtime control demonstrates the ordering mechanism across the worker/NodeDO boundary or replaces the race with one atomic/conditional database mutation.
References
Context
#364 deliberately holds/reverts #360 from the next engine release. #360 intended to preserve tags set by trusted workspace-key enrollment across later bare
node.register, but it merged with unresolved correctness and authority findings.The machine enrollment work in #363 does not close them. It adds
serializeMachineEnroll(workspaceId, machineId)under a['machine', workspaceId, machineId]key, while node-control registration usesserializeNodeOp(workspaceId, nodeId)under a deliberately separate['node', workspaceId, nodeId]key. Enrollment withoutmachine_idtakes no machine lock. ThuscreateNodeTokenandregisterNodecan still interleave, and the process-local queues also do not order separate Cloudflare isolates.Required behavior
Restore the useful part of #360 without inferring authority from a string prefix:
cloud:*string does not become trusted or permanently preserved merely because it was already persisted.cloud:*namespace; documented non-repo:client tags must retain their existing round-trip/update behavior.Design constraints
nodes.tagsprefixes.README.mdandopenapi.yamltogether for any public tag contract change.Discriminating controls
Use deterministic interleaving tests, not SQL/string assertions:
cloud:*tag is not promoted to enrollment-owned authority.repo:cloud:*client tag can still be updated/removed by registration.References
nodeLock.tsuses separate node and machine namespaces; it is not this fix)