github-devloop implementation for #30 - #32
Conversation
|
github-devloop PR child open |
|
github-devloop PR is ready for review |
|
github-devloop PR is ready for review |
|
github-devloop PR review decision: reject teleology: parsimony: fidelity: natural-ownership: proportional-containment: ⟦AI:FKST⟧ |
YueZh127
left a comment
There was a problem hiding this comment.
Independent review of 5a3c5b6477381d084976f0fea52a5278f1024b98
Verdict: REQUEST CHANGES. This review is against qa-tools-fkst-staging@748a8e71b0a8b5626dffdedded7778b67ec52382 and the frozen acceptance criteria in #30.
P0
-
The head does not compile and three required CI jobs are blocked.
control-plane/src/storage/mongo-repository.ts:156-164and:181-185use$inc,$push, and$pullthroughCollection<Document>, which does not type those machine fields.npm run build/npm run typecheckfail withTS2769/TS2322; Contract/build, Test, and Runtime images are red. Define an accurate machine document type rather than hiding the update shape behind a broad cast, then rerun every gate. -
Pre-upgrade active Tasks lose worker authority and can permanently poison the lease sweeper.
Existing
claimed/runningrecords haveworkerId,leaseToken, andmachineId, but noclaimId/claimGeneration; machines haveactiveLeasesbut noleaseReservations.getWorkerTask()rejects those workers,reconcileClaims()skips them, and expiry reachesclaimGuard()and throws before requeue/release. The existingcontrol-plane/src/index.test.tsreproduces this: the expired Task remainsrunningand the sweep logslease generation is no longer active. Add an explicit idempotent, batch-bounded legacy migration/drain/quarantine path using real old-format Mongo documents; one bad record must not stop the whole sweep.
P1
-
A stale expiry CAS can overwrite a successfully committed heartbeat.
TaskClaimGuardchecks only status/claim identity. For arunningTask, a heartbeat can authenticate before expiry and commit a laterleaseExpiresAt, after an expiry sweeper has read the old value; the sweeper's subsequentrunning -> submittedreplacement still matches and removes the renewed lease. This permits generation N+1 to start while the generation-N worker just received a successful heartbeat. Guard lifecycle writes with a monotonic Task record version or the exact observed lease/version; add a deterministic heartbeat-vs-expiry barrier contract for Memory and Mongo. -
The reconciler can delete a just-created live machine reservation/profile lock from a stale global Task snapshot.
reconcileClaims()snapshots Tasks, then later scans machines/profiles. A claimant can commit between those reads; cleanup attask-service.ts:343-361treats the new projection as orphaned and removes it.ensureClaimProjections()ignores a falserenewMachineLease()and only rechecks the Task, so claim may still return success without capacity ownership. Re-read authoritative Task immediately before guarded cleanup and verify every projection before returning a lease. Add claim-vs-multiple-reconciler barrier tests. -
Token rotation can overwrite atomic machine accounting.
server.ts:383-389reads a Machine then calls full-documentsaveMachine(). A concurrent claim/release can updateactiveLeases/leaseReservations, after which rotation replaces them with stale values. Introduce a dedicated atomic token update and separate create/registry mutation from lease authority. Run identical rotate-vs-claim/release contracts against Memory and Mongo. -
Generic compensation breaks interactive/closing lifecycle semantics.
reconcileClaims()callsabortClaim()for every active state. Aclosingsession can be changed back tosubmitted; a requeued interactive Task with adispatchedaction does not callrequeueSessionAction(), leaving the action permanently undispatchable. Compensation must converge by state: preserve the existing closing terminal path and restore dispatched interactive actions when a claim is requeued, without implementing #27 early. -
Reconciliation is full-database and unbounded.
Every claim synchronously scans all Tasks, machines, and profiles; every sweep runs that scan before and after expiry. The existing serialized-sweep contract now observes 10
listTasks()calls where it expected 2. Use indexed active-intent queries, fixed batch limits, and durable retry/cursor state. Keep full-database repair off the worker claim hot path. -
The shared tests do not implement the frozen acceptance matrix.
The three new cases do not barrier the actual machine/profile CAS boundaries, do not reconnect Mongo for restart, and do not inject interruption after each persistence boundary. Missing cases include delayed claimant after heartbeat/action dispatch, response loss, profile races between different Tasks, unrelated authoritative ownership and release, stale-reconciler and token-rotation TOCTOU, queue/deadline/interactive preservation, and HTTP/Session/webhook/log/fixture/OpenAPI non-disclosure. Add deterministic boundary fault injection and real Mongo reconnect coverage.
P2
-
Memory/Mongo repository behavior differs.
Memory idempotent reservation identity omits
taskId, while Mongo includes it. Memory returns true for a matched no-op submitted replacement, while Mongo usesmodifiedCountand returns false. Align the identity comparator and guard-success semantics, and cover both through the shared contract. -
Restart compensation cannot preserve pre-claim queue priority.
Claim removes queuePriority; after a crash, the reconciler has lost it and hard-codes -1. Persist the pre-claim priority in the durable claim intent or retain it until projection commit, then add a crash-after-Task-CAS ordering test.
Verification evidence
npm run lint: passed.- Focused Task/HTTP tests: 14/14 passed outside the restricted loopback sandbox.
npm run test:mongo-contract: 20/20 passed, but the new matrix above is missing.npm test: 296 passed, 2 failed (periodically expires active leases; serialized sweep call-count contract).npm run typecheck: failed with the Mongo update typing errors above.- GitHub CI: Contract/build failed; Test failed before Vitest; Runtime images failed before smoke; Contract demo passed.
Please fix these in the same FKST work item/PR. After a new head is published, the same independent reviewer must re-review the complete diff. Do not merge to staging, and do not route #29 yet.
|
github-devloop merge gate failed: unguarded machine replacement |
|
github-devloop PR is ready for review |
|
github-devloop PR review decision: approve teleology: parsimony: natural-ownership: proportional-containment: Advisory (non-blocking): ⟦AI:FKST⟧ |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
YueZh127
left a comment
There was a problem hiding this comment.
Independent re-review of a903adac3e904428e0c39edb537d0d27239d7711
Verdict: REQUEST CHANGES. Base remains qa-tools-fkst-staging@748a8e71b0a8b5626dffdedded7778b67ec52382. The fix delta is only +44/-2 in three repository files. It closes neither the full #30 acceptance matrix nor the prior review.
P0
-
Build/typecheck remains broken.
control-plane/src/storage/mongo-repository.ts:167-175,192-196still applies$inc/$push/$pullthroughCollection<Document>, producingTS2769/TS2322. Contract/build, Test, and Runtime images are still failed. Use a real typed Mongo machine document; do not cast the invalid shape away. -
Legacy active Tasks still lose authority and poison the sweeper. Old
running/claimeddocuments have noclaimId/claimGeneration. They are rejected by worker APIs, skipped by reconciliation, and throw fromclaimGuard()on expiry. The existingcontrol-plane/src/index.test.ts:149-159still fails and leaves the Task running. Implement idempotent, batch-bounded migration/drain/quarantine with old-format Mongo fixtures; one bad record must not abort the sweep.
P1
-
Heartbeat-versus-expiry stale overwrite remains.
TaskClaimGuardstill only checks status/claim identity. A heartbeat authenticated before expiry can commit and return a later expiry, then a sweeper holding the oldrunningsnapshot can still replace it withsubmitted. Add monotonic Task record version or equivalent exact-version CAS, plus a deterministic Memory/Mongo barrier test. -
A stale reconciliation snapshot can still delete newly created live projections.
task-service.ts:336-361snapshots all Tasks, then cleans later machine/profile reads from that stale map. Re-read authoritative Task before cleanup.ensureClaimProjections()also ignores false fromrenewMachineLease()and validates only Task state before returning; verify machine and profile authority too. -
The token-rotation fix changes generic
saveMachine()semantics and breaks existing behavior.memory-repository.ts:110-120andmongo-repository.ts:146-158now silently ignore passed accounting for every existing Machine. Full tests prove the regression:server.test.tscannot set active occupancy, andtesting-attempt-service.test.tswrongly admits Testing work onto a machine intended to have a generic lease. Restore an explicit complete create/save contract and add a dedicated atomicrotateMachineToken/metadata mutation used by both HTTP routes; test rotate-vs-reserve/release barriers. -
saveProfile()can still erase profile claim authority. Two concurrent create calls can both observe absence; after one create and a Task'sacquireProfileLease(), the delayed fullsaveProfile()clearslockedByTaskId, claim identity, and expiry. Make profile create insert-only/CAS and keep metadata mutations authority-preserving. Add create-vs-acquire Memory/Mongo barriers. -
Generic compensation still breaks interactive/closing state.
reconcileClaims()sends every projection failure throughabortClaim():closingbecomessubmitted, and an interactivedispatchedaction is not requeued. Converge by state, preserving close semantics and restoring dispatched actions on requeue without implementing #27 early. -
Reconciliation is still full-database and unbounded on claim and sweep hot paths. Every claim and both ends of every sweep scan all Tasks/machines/profiles. Use indexed active intents, fixed batches, and durable retry/cursor state. The failing sweep call-count contract is evidence, not an assertion to loosen.
-
The acceptance test matrix is still missing. The added stale-metadata case does not cover actual machine/profile CAS barriers, two different Task races, every persistence-boundary fault, heartbeat/expiry, claim/reconciler, response loss, real Mongo reconnect, unrelated authoritative ownership, queue/deadline/interactive preservation, or all public non-disclosure surfaces.
P2
-
Memory/Mongo semantics still differ. Memory reservation idempotency omits
taskId; Mongo includes it. Memory returns true for matched no-op submitted CAS; Mongo usesmodifiedCountand returns false. Align shared identity and guard-match semantics. -
Restart compensation still loses original queue priority. Claim clears
queuePriority; after crash the reconciler can only force-1. Persist pre-claim priority in durable intent or retain it until projection commit, and test restart ordering.
Round 2 evidence
npm run lint: pass.npm run typecheck: fail at Mongo lines 167/195.npm run test:mongo-contract: 22/22 pass, but the required matrix remains absent.npm test: 296 pass / 4 fail: legacy expiry, sweep scan count, HTTP machine accounting, and Testing/generic capacity isolation.- GitHub: Contract/build failed; Test failed before Vitest; Runtime images failed before smoke; Contract demo passed.
Prior findings 1/2/3/4/6/7/8/9/10 are untouched. Prior finding 5 is only partially addressed and has introduced the two test regressions above. Do not merge this head. Fix in the same PR, publish a new immutable head, and return it to the same reviewer for a complete base-to-head review. Keep #29 unrouted.
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
|
github-devloop merge gate wait: ci-unknown |
github-devloop implementation PR for issue #30