mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-27 07:51:52 +00:00
TASK-f40126ee: bind adoption lifecycle revisions
This commit is contained in:
1 parent
18a1b4a2f5
commit
7533d86462
3 files changed
+69
-2
No files matched your search
@@ -3199,3 +3199,47 @@ test_integrity_note:
|
||||
did_relax_any_assertion: false
|
||||
if_true_explain_spec_basis: ""
|
||||
```
|
||||
|
||||
```yaml
|
||||
test_integrity_note:
|
||||
change_type: new_tests
|
||||
scenarios:
|
||||
- id: SCN-integration-adoption-lifecycle-revision-cas
|
||||
given: "A prepared or finalized owner-bound adoption record with a known journal revision"
|
||||
when: "A caller supplies a stale expectedRecordRevision to finalize or request native qualification"
|
||||
then: "The operation refuses before private-ref or queue effects; the original refs, journal state, and queue remain unchanged"
|
||||
observable_outcomes:
|
||||
- "ADOPTION_RECORD_REVISION_STALE is returned for stale lifecycle callers"
|
||||
- "Private candidate/archive refs remain absent before stale finalize and no integration request is created by stale native request"
|
||||
determinism_controls:
|
||||
- "Disposable local Git repository and temporary SQLite-backed control-plane fixture; no network, provider, or live adoption"
|
||||
anti_cheat_rationale:
|
||||
prevents:
|
||||
- "Issuing external ref or queue effects from a stale lifecycle read"
|
||||
- "Treating journal revision as an internal-only implementation detail"
|
||||
relaxation:
|
||||
did_relax_any_assertion: false
|
||||
if_true_explain_spec_basis: ""
|
||||
```
|
||||
|
||||
```yaml
|
||||
test_integrity_note:
|
||||
change_type: spec_correction
|
||||
scenarios:
|
||||
- id: SCN-integration-adoption-request-replay
|
||||
given: "Two authenticated callers carry the same finalized adoption revision into the serialized native-request transition"
|
||||
when: "One caller completes the transition and the other resumes with its now-stale expectedRecordRevision"
|
||||
then: "The second caller strictly receives ADOPTION_RECORD_REVISION_STALE while exactly one requested journal record and one queue row remain"
|
||||
observable_outcomes:
|
||||
- "The replay loser reports the stale journal-revision boundary, not a generic post-state refusal"
|
||||
- "Exactly one private-ref integration request and requested audit transition exist"
|
||||
determinism_controls:
|
||||
- "Two independently opened local API processes over disposable Git and SQLite fixture state"
|
||||
anti_cheat_rationale:
|
||||
prevents:
|
||||
- "Accepting a stale lifecycle read after another caller has committed"
|
||||
- "Relaxing the exactly-one-request invariant after adding revision binding"
|
||||
relaxation:
|
||||
did_relax_any_assertion: false
|
||||
if_true_explain_spec_basis: ""
|
||||
```
|
||||
@@ -88,6 +88,12 @@ function exactInput(record, input) {
|
||||
if (mismatches.length) throw new TorchError('Lifecycle input cannot override immutable prepared adoption fields', {
|
||||
code: 'ADOPTION_IMMUTABLE_BINDING_STALE', details: { adoptionId: record.id, fields: mismatches },
|
||||
});
|
||||
if (!Number.isInteger(input.expectedRecordRevision) || input.expectedRecordRevision !== record.revision) {
|
||||
throw new TorchError('Lifecycle input must bind the current adoption record revision', {
|
||||
code: 'ADOPTION_RECORD_REVISION_STALE',
|
||||
details: { adoptionId: record.id, expectedRevision: input.expectedRecordRevision ?? null, actual: record.revision },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,6 +340,7 @@ export class AdoptionService {
|
||||
this.controlPlane.database.exec('BEGIN IMMEDIATE');
|
||||
try {
|
||||
const record = this.getPrepared(adoptionId);
|
||||
exactInput(record, input);
|
||||
if (!['finalized', 'requesting'].includes(record.state)) {
|
||||
throw new TorchError('Only a finalized adoption can request native qualification', {
|
||||
code: 'ADOPTION_REQUEST_STATE_INVALID', details: { state: record.state },
|
||||
|
||||
@@ -130,7 +130,8 @@ function prepare(context, adoptionId = 'fixture-adoption') {
|
||||
|
||||
function lifecycleInput(context, action, adoptionId, extra = {}) {
|
||||
const { approvalId, approvalRevision, adapter } = authority(context, { action, adoptionId });
|
||||
return { actorId: 'owner', adoptionId, approvalId, approvalRevision, adapter, ...extra };
|
||||
const record = context.service.getPreparedAdoption(adoptionId);
|
||||
return { actorId: 'owner', adoptionId, expectedRecordRevision: record.revision, approvalId, approvalRevision, adapter, ...extra };
|
||||
}
|
||||
|
||||
function blockedCodes(error) {
|
||||
@@ -167,6 +168,17 @@ test('SCN-integration-adoption-journal-and-binding: a duplicate preparation cann
|
||||
(error) => error.code === 'ADOPTION_RECORD_REVISION_STALE');
|
||||
assert.throws(() => context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId, { candidateSha: context.base })),
|
||||
(error) => error.code === 'ADOPTION_IMMUTABLE_BINDING_STALE');
|
||||
assert.throws(() => context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId, {
|
||||
expectedRecordRevision: prepared.revision + 1,
|
||||
})), (error) => error.code === 'ADOPTION_RECORD_REVISION_STALE');
|
||||
const refs = context.service.adoption.refAdapter.readRefs([
|
||||
prepared.expectedOldRef, prepared.candidateRef, prepared.archiveRef,
|
||||
]);
|
||||
assert.deepEqual(refs, {
|
||||
[prepared.expectedOldRef]: context.base,
|
||||
[prepared.candidateRef]: null,
|
||||
[prepared.archiveRef]: null,
|
||||
});
|
||||
const { mutationPerformed, ...durablePrepared } = prepared;
|
||||
assert.equal(mutationPerformed, true);
|
||||
assert.deepEqual(context.service.adoption.getPrepared(adoptionId), durablePrepared);
|
||||
@@ -243,6 +255,10 @@ test('SCN-integration-adoption-private-ref-intake: finalized private refs enter
|
||||
const adoptionId = 'fixture-adoption';
|
||||
prepare(context, adoptionId);
|
||||
const finalized = context.service.finalizeAdoption(lifecycleInput(context, 'finalize', adoptionId));
|
||||
assert.throws(() => context.service.requestAdoptedCandidate(lifecycleInput(context, 'request', adoptionId, {
|
||||
expectedRecordRevision: finalized.revision + 1,
|
||||
})), (error) => error.code === 'ADOPTION_RECORD_REVISION_STALE');
|
||||
assert.deepEqual(context.service.list(), []);
|
||||
const requested = context.service.requestAdoptedCandidate(lifecycleInput(context, 'request', adoptionId));
|
||||
assert.equal(requested.state, 'requested');
|
||||
assert.equal(requested.integrationRequest.sourceBranch, finalized.candidateRef);
|
||||
@@ -313,7 +329,7 @@ test('SCN-integration-adoption-request-replay: concurrent authenticated API call
|
||||
concurrentAdoptedRequest(context, requestInput),
|
||||
]);
|
||||
assert.deepEqual(outcomes.map((entry) => entry.outcome).sort(), ['refused', 'requested']);
|
||||
assert.equal(outcomes.find((entry) => entry.outcome === 'refused').code, 'ADOPTION_REQUEST_STATE_INVALID');
|
||||
assert.equal(outcomes.find((entry) => entry.outcome === 'refused').code, 'ADOPTION_RECORD_REVISION_STALE');
|
||||
const requests = context.service.list();
|
||||
assert.equal(requests.length, 1);
|
||||
assert.equal(context.service.getPreparedAdoption(adoptionId).state, 'requested');
|
||||
|
||||
Reference in new issue
Block a user