test(handoff): bind PDF diagnostics and constrain entry proposal
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
# Proposed evidence adapter v1
|
# Proposed evidence adapter v1
|
||||||
|
|
||||||
|
P10-ENTRY-FINAL addendum: the frozen source manifest also covers tracked Python/JavaScript PDF diagnostics in `docs/p10-entry-final/`. Omitting a diagnostic from both source manifests must fail verification. `ENTRY_PROPOSAL_SCHEMA.json` describes a separate evidence-only owner decision proposal; its constants prohibit execution permission, approved removals or a GO decision. It grants no authority and does not replace the controller's protected approvals.
|
||||||
|
|
||||||
Contract ID: `urn:jobjakt:pre-phase10-evidence:1`. This is a **proposed evidence import contract**, not a replacement for AUTONOMOUS-ROADMAP-01's committed `automation/schemas/backlog.schema.json` or `result.schema.json`. Both happen to use version1; their document kinds and authority are different. The separate controller snapshot is recorded in GATES.json. Its worktree/policy/state/runtime was not modified or executed.
|
Contract ID: `urn:jobjakt:pre-phase10-evidence:1`. This is a **proposed evidence import contract**, not a replacement for AUTONOMOUS-ROADMAP-01's committed `automation/schemas/backlog.schema.json` or `result.schema.json`. Both happen to use version1; their document kinds and authority are different. The separate controller snapshot is recorded in GATES.json. Its worktree/policy/state/runtime was not modified or executed.
|
||||||
|
|
||||||
| Package field | Existing controller integration |
|
| Package field | Existing controller integration |
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "urn:jobjakt:p10-entry-final:proposal:1",
|
||||||
|
"title": "Evidence-only Phase10 entry proposal; never an approval",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["schemaVersion", "workstreamId", "authority", "candidateCommit", "acceptedBranch", "applicationFixCommit", "packageHeadRule", "entryDecision", "eligibleForAutomaticExecution", "cleanupScope", "pdfDecisions", "outstandingBlockerIds", "deferralRequests", "requiredDecisions", "testEvidence", "publication", "productionDecision", "generatedUtc"],
|
||||||
|
"properties": {
|
||||||
|
"schemaVersion": { "const": 1 },
|
||||||
|
"workstreamId": { "const": "P10-ENTRY-FINAL" },
|
||||||
|
"authority": { "const": "EVIDENCE_ONLY_NOT_OWNER_APPROVAL" },
|
||||||
|
"candidateCommit": { "$ref": "#/definitions/commit" },
|
||||||
|
"acceptedBranch": { "const": "test/pre-phase10-ux-acceptance" },
|
||||||
|
"applicationFixCommit": { "$ref": "#/definitions/commit" },
|
||||||
|
"packageHeadRule": { "const": "Resolve final clean published envelope HEAD independently; bind it, the candidate snapshot, exact scope, policy and artifact hashes in protected approval. No self-referential commit claim." },
|
||||||
|
"entryDecision": { "const": "NO-GO" },
|
||||||
|
"eligibleForAutomaticExecution": { "const": false },
|
||||||
|
"productionDecision": { "const": "NO-GO" },
|
||||||
|
"cleanupScope": {
|
||||||
|
"type": "object", "additionalProperties": false,
|
||||||
|
"required": ["disposition", "inventory", "proposedFirstAction", "approvedRemovalCandidates", "implementationPermission"],
|
||||||
|
"properties": {
|
||||||
|
"disposition": { "const": "ASSESSMENT_ONLY_NO_REMOVALS_APPROVED" },
|
||||||
|
"inventory": { "type": "string", "minLength": 1 },
|
||||||
|
"proposedFirstAction": { "type": "string", "minLength": 1 },
|
||||||
|
"approvedRemovalCandidates": { "type": "array", "maxItems": 0 },
|
||||||
|
"implementationPermission": { "const": false }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pdfDecisions": {
|
||||||
|
"type": "object", "additionalProperties": false,
|
||||||
|
"required": ["originalCorpus", "actualAccentClippingFix", "unchangedRevisedOracle", "proposedBaselineDiagnostic", "metricDisposition", "humanVisual", "actualPrinting"],
|
||||||
|
"properties": {
|
||||||
|
"originalCorpus": { "const": "PASS" },
|
||||||
|
"actualAccentClippingFix": { "const": "PASS_AUTOMATED_FINITE_CORPUS" },
|
||||||
|
"unchangedRevisedOracle": { "const": "FAIL" },
|
||||||
|
"proposedBaselineDiagnostic": { "const": "PASS_DIAGNOSTIC_ONLY_NOT_GATE_REPLACEMENT" },
|
||||||
|
"metricDisposition": { "const": "OWNER_DECISION_REQUIRED" },
|
||||||
|
"humanVisual": { "const": "NOT_TESTED" },
|
||||||
|
"actualPrinting": { "const": "NOT_TESTED" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outstandingBlockerIds": { "$ref": "#/definitions/strings" },
|
||||||
|
"deferralRequests": {
|
||||||
|
"type": "array", "items": {
|
||||||
|
"type": "object", "additionalProperties": false, "required": ["id", "scope", "sourceAuthority", "state"],
|
||||||
|
"properties": { "id": { "type": "string" }, "scope": { "type": "string" }, "sourceAuthority": { "type": "string" }, "state": { "const": "REQUESTED_NOT_GRANTED_OR_EXECUTABLE" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requiredDecisions": {
|
||||||
|
"type": "array", "minItems": 1, "items": {
|
||||||
|
"type": "object", "additionalProperties": false, "required": ["id", "action", "state", "authority"],
|
||||||
|
"properties": { "id": { "type": "string" }, "action": { "type": "string" }, "state": { "const": "PENDING" }, "authority": { "enum": ["EXPLICIT_OWNER_REFERENCE_DISPOSITION", "EXTERNAL_PROTECTED_OWNER_OR_OPERATOR_DECISION"] } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"testEvidence": { "$ref": "#/definitions/strings" },
|
||||||
|
"publication": {
|
||||||
|
"type": "object", "additionalProperties": false, "required": ["status", "candidateRemoteCommit", "observedUtc", "finalHeadMustBeRechecked"],
|
||||||
|
"properties": { "status": { "const": "VERIFIED_CANDIDATE_SNAPSHOT" }, "candidateRemoteCommit": { "$ref": "#/definitions/commit" }, "observedUtc": { "type": "string", "format": "date-time" }, "finalHeadMustBeRechecked": { "const": true } }
|
||||||
|
},
|
||||||
|
"generatedUtc": { "type": "string", "format": "date-time" }
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"commit": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
|
||||||
|
"strings": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,6 +83,21 @@ test('omitting a source file from both manifests cannot claim a complete freeze'
|
|||||||
test('artifact paths cannot escape the imported package', () => shadow(base => {
|
test('artifact paths cannot escape the imported package', () => shadow(base => {
|
||||||
json(base, 'TEST_EVIDENCE', index => { index.artifacts[0].path = '../../outside'; });
|
json(base, 'TEST_EVIDENCE', index => { index.artifacts[0].path = '../../outside'; });
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
test('PDF diagnostic code cannot be omitted from both frozen source manifests', () => shadow(base => {
|
||||||
|
json(base, 'TEST_EVIDENCE', index => {
|
||||||
|
for (const id of [index.sourceManifestArtifactId, index.sourceAfterArtifactId]) {
|
||||||
|
const artifact = index.artifacts.find(item => item.id === id);
|
||||||
|
const file = path.join(base, artifact.path);
|
||||||
|
const data = JSON.parse(zlib.gunzipSync(fs.readFileSync(file)).toString('utf8'));
|
||||||
|
const name = Object.keys(data).find(item => item.startsWith('docs/p10-entry-final/') && item.endsWith('.py'));
|
||||||
|
assert.ok(name, 'PDF diagnostic source must be bound before qualification');
|
||||||
|
delete data[name];
|
||||||
|
const bytes = zlib.gzipSync(JSON.stringify(data));
|
||||||
|
fs.writeFileSync(file, bytes); artifact.sha256 = hash(bytes);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
test('an imported schema cannot weaken the trusted schema', () => shadow(base => {
|
test('an imported schema cannot weaken the trusted schema', () => shadow(base => {
|
||||||
fs.writeFileSync(path.join(base, 'HANDOFF_SCHEMA.json'), '{}');
|
fs.writeFileSync(path.join(base, 'HANDOFF_SCHEMA.json'), '{}');
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -166,9 +166,10 @@ export function validatePackage(base = directory) {
|
|||||||
const after = JSON.parse(artifact(evidence.sourceAfterArtifactId).toString('utf8'));
|
const after = JSON.parse(artifact(evidence.sourceAfterArtifactId).toString('utf8'));
|
||||||
demand(JSON.stringify(source) === JSON.stringify(after), 'Source drifted during the regression run');
|
demand(JSON.stringify(source) === JSON.stringify(after), 'Source drifted during the regression run');
|
||||||
const prefixes = ['JobTrackerApi/', 'JobTrackerApi.Tests/', 'JobTrackerApi.MariaDb.Tests/', 'job-tracker-ui/', 'scripts/', 'tools/summarizer/', 'docs/pre-phase10-ux/', 'docs/redesign/phase-9/'];
|
const prefixes = ['JobTrackerApi/', 'JobTrackerApi.Tests/', 'JobTrackerApi.MariaDb.Tests/', 'job-tracker-ui/', 'scripts/', 'tools/summarizer/', 'docs/pre-phase10-ux/', 'docs/redesign/phase-9/'];
|
||||||
const tracked = execFileSync('rtk', ['proxy', 'git', 'ls-files', '-z'], { cwd: root, encoding: 'utf8' }).split('\0').filter(name => prefixes.some(prefix => name.startsWith(prefix))).sort();
|
const entryDiagnostic = name => name.startsWith('docs/p10-entry-final/') && /\.(?:py|cjs)$/.test(name);
|
||||||
|
const tracked = execFileSync('rtk', ['proxy', 'git', 'ls-files', '-z'], { cwd: root, encoding: 'utf8' }).split('\0').filter(name => prefixes.some(prefix => name.startsWith(prefix)) || entryDiagnostic(name)).sort();
|
||||||
demand(Object.keys(source).length <= 5000 && JSON.stringify(Object.keys(source).sort()) === JSON.stringify(tracked), 'Frozen source membership does not match the tracked qualification scope');
|
demand(Object.keys(source).length <= 5000 && JSON.stringify(Object.keys(source).sort()) === JSON.stringify(tracked), 'Frozen source membership does not match the tracked qualification scope');
|
||||||
execFileSync('rtk', ['proxy', 'git', 'diff', '--quiet', evidence.testedCommit, '--', ...prefixes], { cwd: root });
|
execFileSync('rtk', ['proxy', 'git', 'diff', '--quiet', evidence.testedCommit, '--', ...prefixes, ...tracked.filter(entryDiagnostic)], { cwd: root });
|
||||||
for (const [name, expected] of Object.entries(source)) {
|
for (const [name, expected] of Object.entries(source)) {
|
||||||
demand(!path.isAbsolute(name) && !name.split(/[\\/]/).includes('..'), 'Unsafe source path');
|
demand(!path.isAbsolute(name) && !name.split(/[\\/]/).includes('..'), 'Unsafe source path');
|
||||||
const file = path.resolve(root, name);
|
const file = path.resolve(root, name);
|
||||||
|
|||||||
Reference in New Issue
Block a user