test(handoff): add fail-closed evidence schema and qualification records

This commit is contained in:
cesnimda
2026-10-29 18:30:53 +01:00
parent 8804231be4
commit 78e5296881
16 changed files with 2068 additions and 909 deletions
@@ -0,0 +1,2 @@
# Preserve source instructions and hash-indexed evidence identically across Windows/Linux checkouts.
* -text
@@ -0,0 +1,29 @@
# AI and parser qualification reconciliation
## What was actually exercised
| Layer | Evidence / decision | What it does not establish |
|---|---|---|
| Real Docling parsing | Fresh14 real document-extractor tests; actual PDF/DOCX import browser lanes and live local PDF worker. Native extraction uses pinned model-free Docling | OCR/layout-model or arbitrary-document completeness |
| Deterministic interpretation | Frozen benchmark evidence remains separately attributed: PDF typed precision100%, recall81.967%; DOCX122/122 typed facts. Fresh oracle/structural helper tests validate their own logic | Population accuracy, real model quality, or every unusual layout |
| Review/correction/application | Real browser/MariaDB/RD21 lanes exercise approved facts, duplicates, rollback, stale writes and historical preservation | Automatic model semantic truth or human usability approval |
| Generic queued strategy | Fresh actual local operation succeeded with saved nonempty160-character summary; admin retained a prior provider_unavailable event | First-attempt reliability, latency guarantees or factual CV interpretation |
| Scheduled job enrichment | Fresh actual scheduler persisted summary681characters plus tags, then read back through API | Semantic quality or external provider/production acceptance |
| Optional CV model adapter | Three real isolated Qwen requests completed but all three were rejected with ValueError by the adapter. **Compatibility and factual accuracy remain BLOCKED** | Zero accepted suggestions is not zero hallucinations or100% precision |
| Active CV processing configuration | CV_IMPORT_AI_ENABLED=false; adapter remains disconnected from active deterministic processing | No model activation or owner security-risk acceptance |
## Real adapter probe
[AI_EVALUATION_PLAN.md](AI_EVALUATION_PLAN.md) records authority and guards before inference. The official local qwen2.5:7b Q4_K_M manifest845dbda0ea48ed749caafd9e6037047aa19acfcfd82e704d7ca97d631a0b697e and all stored blobs were hash-verified. A disposable Ollama instance reused those bytes through a **read-only** model-volume mount, with no published ports, internal-only network, cloud disabled, four CPUs/8GiB, one loaded model/parallel request and no daemon socket. A bounded read-only Python client exercised the unchanged adapter using its real transport, JSON schema, source grounding and120-second timeout. Existing development Ollama/services/configuration/cache were not changed. Exact generated containers and the empty network were removed, with cleanup recorded.
EN, Bokmål and instruction-in-source cases took79.009,47.308 and35.811 seconds respectively. All returned the explicit rejected/failed outcome. No raw response was retained by this first probe, so the precise invalid suggestion/schema distinction is not claimed. No accepted semantic precision/recall is computed. These three independently authored development probes are not the frozen corpus and are not PDF/DOCX end-to-end model evaluations. They supply genuine negative compatibility evidence; they do not replace the outstanding full qualification.
Raw source-bound process/results/cases/client hashes are in `.artifacts/automation-handoff-01/model-probe-01/`; portable evidence is indexed in TEST_EVIDENCE.json. The probe process exit0 means the measurement completed and cleanup ran, **not that AI acceptance passed**.
The local runtime's original model volume was found writable despite a read-only container root. This finding informed the isolated read-only mirror above; no stronger existing-cache protection is inferred. Artifact integrity and scoped known-advisory evidence are independent of whole-image/native sandbox security. A fresh read-only inventory of the exact current sidecar image found63 installed distributions;126 exact OSV/PyPI queries returned zero advisory records and one retained PyPI404 for torch2.13.0+cpu. The new raw audit remains INCOMPLETE. Python remains CONDITIONAL under the retained P9-SEC-02 disposition; neither this probe, this incomplete scan nor import-lock zero advisories closes native/whole-image/production qualifications. No package was resolved or installed by the audit.
Future owner-approved qualification must use immutable approved artifacts/runtime and identical frozen documents/annotations, source and prompt hashes; measure precision/recall, record association, dates, hallucinated/unsupported claims, provenance, duplicate behavior, multilingual semantics and review burden. Add missing/malformed output, unavailable service, timeout/cancel/retry, injection, resource and cache-integrity probes. Keep deterministic, model-assisted, user-corrected and persisted outcomes separate. Do not select a permanent general-purpose model or enable the adapter from these results.
## Existing generic-AI precondition
StrategySnapshotService currently requires nonempty legacy ProfileCvText in both admission and generation. The reviewed Docling apply path persists canonical structured facts and deliberately does not overwrite that user-controlled text. Therefore the successful strategy smoke used separately authored fictional CV text as an explicit precondition; it does **not** prove strategy generation from a canonical-only imported profile. Career Profile → CV Editor → PDF is independently tested and does not require this legacy text. Product Review should assess this generic-AI source compatibility as a separate bounded backlog item; do not silently populate legacy text from unreviewed uploaded content or claim every AI option works for every profile/account.
@@ -0,0 +1,307 @@
{
"schemaVersion": 1,
"workstreamId": "AUTOMATION-HANDOFF-01",
"kind": "BLOCKERS",
"items": [
{
"id": "P10-ENTRY-01",
"description": "No protected owner approval of the final published development baseline and exact cleanup scope was supplied to this handoff.",
"evidence": [
"AUTHORITY.md",
"docs/cv-integration-qa/PHASE_10_HANDOFF.md"
],
"severity": "HIGH",
"acceptanceCriterion": "Exact baseline/scope and independent stage entry",
"requiredAction": "Review the completed package and candidate scope; grant phase10-development-entry only through the separate protected approval process. A repository GO is insufficient.",
"blockingPhase": [
"PHASE-10"
],
"responsibleParty": "Owner / protected controller approval authority",
"deferralAuthority": null,
"classification": "MANDATORY_ENTRY",
"status": "BLOCKED"
},
{
"id": "P10-CANDIDATE-01",
"description": "No proposed product deletion has proven absence of static, dynamic, route and historical-data consumers.",
"evidence": [
"CLEANUP_INVENTORY.md",
"docs/redesign/IMPLEMENTATION_PLAN.md"
],
"severity": "HIGH",
"acceptanceCriterion": "Unused-route proof; passing affected tests; satisfied functionality inventory",
"requiredAction": "Prepare and approve a concrete candidate with caller/route/inventory/rollback proof and affected native regression; retain every active compatibility path.",
"blockingPhase": [
"PHASE-10: each removal candidate"
],
"responsibleParty": "Development owner / reviewer",
"deferralAuthority": null,
"classification": "MANDATORY_ENTRY",
"status": "BLOCKED"
},
{
"id": "AUTO-RUNTIME-01",
"description": "The separate orchestrator reports an unqualified native Codex sandbox and missing isolated runner/signing boundary.",
"evidence": [
"AUTHORITY.md",
"ROADMAP.md"
],
"severity": "HIGH",
"acceptanceCriterion": "Independent qualified execution boundary",
"requiredAction": "Qualify the sandbox/isolated account or VM, protected approval/state ACLs, process/CPU/memory/tool/read/egress/secret boundaries and actual worker execution. This workstream must not modify that runtime.",
"blockingPhase": [
"AUTOMATIC_PHASE-10_EXECUTION"
],
"responsibleParty": "AUTONOMOUS-ROADMAP-01 platform operator",
"deferralAuthority": null,
"classification": "MANDATORY_ENTRY",
"status": "BLOCKED"
},
{
"id": "AUTO-ADAPTER-01",
"description": "The external controller real-stage test adapter is unconfigured; synthetic orchestration tests are not application qualification.",
"evidence": [
"ROADMAP.md",
"READY_TO_RUN.md"
],
"severity": "HIGH",
"acceptanceCriterion": "Qualified real-stage composite test adapter",
"requiredAction": "Install/review actual commands and parsers, source and suite membership, report freshness and failure/skip/retry/cleanup policy. Independently rerun candidate checks; do not set configured=true as a bypass.",
"blockingPhase": [
"AUTOMATIC_PHASE-10_EXECUTION"
],
"responsibleParty": "AUTONOMOUS-ROADMAP-01 test-adapter maintainer",
"deferralAuthority": null,
"classification": "MANDATORY_ENTRY",
"status": "BLOCKED"
},
{
"id": "PDF-S01-01",
"description": "Revised-template S01 DOM Range versus PDF character-cell coordinate failures remain undispositioned. Raster agreement is a different measurement.",
"evidence": [
"docs/pre-phase10-ux/ACCEPTANCE.md",
"VISUAL_REVIEW.md"
],
"severity": "MODERATE",
"acceptanceCriterion": "Required preview/PDF fidelity and visual spacing qualification",
"requiredAction": "Retain original oracle and all failures. Investigate/fix an actual measured defect; obtain explicit protected scope/criterion disposition if applicability is ambiguous. Do not waive an automated requirement using a model summary or change goldens to conceal failures.",
"blockingPhase": [
"PHASE-10: document/rendering scope or required PDF gate",
"PRODUCTION"
],
"responsibleParty": "Document QA owner / owner acceptance authority",
"deferralAuthority": null,
"classification": "MANDATORY_ENTRY",
"status": "BLOCKED"
},
{
"id": "HUMAN-01",
"description": "Physical device, keyboard/AT/contrast, human EN/Bokmål, typography and actual printing acceptance are not provided.",
"evidence": [
"MANUAL_ACCEPTANCE.md"
],
"severity": "MODERATE",
"acceptanceCriterion": "Genuine human/device/language/print acceptance",
"requiredAction": "Complete the prioritised manual scenarios; record exact device/AT/language/print evidence outside model-writable approval authority. Candidate-specific regressions still block.",
"blockingPhase": [
"HUMAN_ACCEPTANCE",
"PRODUCTION",
"PHASE-10: affected candidate requires preservation"
],
"responsibleParty": "Owner / human QA",
"deferralAuthority": "docs/redesign/phase-9/REQUEST.md:246–252 permits genuine unfinished human checks as release gates;426–439 requires individual disposition. This is not automatic Phase10 entry or an executable deferral.",
"classification": "CONDITIONAL_DEVELOPMENT",
"status": "CONDITIONALLY_DEFERRED"
},
{
"id": "AI-CV-01",
"description": "The disabled/disconnected CV adapter has no accepted end-to-end semantic qualification. Three real isolated synthetic responses were rejected.",
"evidence": [
"AI_EVALUATION_PLAN.md",
"AI_RESULTS.md",
"docs/cv-import/ACCEPTANCE.md"
],
"severity": "HIGH",
"acceptanceCriterion": "No invented facts; source-grounded factual accuracy and explicit user review",
"requiredAction": "Keep CV_IMPORT_AI_ENABLED=false and active processing deterministic; qualify immutable model/runtime/schema/prompt on frozen real PDF/DOCX corpus, review burden and adversarial cases before activation. Small generic successes do not qualify CV accuracy.",
"blockingPhase": [
"CV_MODEL_ACTIVATION",
"CV_AI_ACCURACY",
"PRODUCTION_AI_QUALITY"
],
"responsibleParty": "AI qualification owner",
"deferralAuthority": "docs/cv-import/ACCEPTANCE.md permits model-independent conditional development with inactive optional adapter; no security-risk waiver or automatic controller deferral.",
"classification": "CONDITIONAL_DEVELOPMENT",
"status": "CONDITIONALLY_DEFERRED"
},
{
"id": "DET-RECALL-01",
"description": "Frozen deterministic PDF typed recall and unresolved-source precision remain partial; source correction is necessary.",
"evidence": [
"docs/pre-phase10-ux/ACCEPTANCE.md"
],
"severity": "MODERATE",
"acceptanceCriterion": "Near-100% is aspirational; preserve unknown/ambiguous facts",
"requiredAction": "Preserve source evidence, unknown values, conservative merges and manual corrections; benchmark changes on frozen corpus separately from model probes.",
"blockingPhase": [
"BROAD_EXTRACTION_ACCURACY_CLAIMS",
"PHASE-10: review/legacy compatibility removal"
],
"responsibleParty": "CV/import QA owner",
"deferralAuthority": "docs/cv-import/ACCEPTANCE.md and docs/cv-integration-qa/ACCEPTANCE.md explicitly condition supported workflow acceptance on these retained limitations.",
"classification": "CONDITIONAL_DEVELOPMENT",
"status": "CONDITIONALLY_DEFERRED"
},
{
"id": "PYSEC-01",
"description": "Full Python dependency security is conditional; retained vendor-local Torch PyPI404 is not a clean two-provider/whole-image verdict.",
"evidence": [
"docs/redesign/phase-9/p9-sec-02/ACCEPTANCE_ADDENDUM_2026-10-25.md",
"docs/redesign/phase-9/p9-sec-02/DISPOSITIONS.md"
],
"severity": "HIGH",
"acceptanceCriterion": "No bypass of Python/model/cache qualification",
"requiredAction": "Retain exact installed OSV/vendor wheel/range/source/model evidence and incomplete response; refresh before affected changes/release. Current import-lock zero advisories does not close full runtime/native security.",
"blockingPhase": [
"PYTHON_UNTRUSTED_PROCESSING_QUALIFICATION",
"PRODUCTION",
"PHASE-10: affected Python/model path"
],
"responsibleParty": "Security / Python qualification owner",
"deferralAuthority": "Existing P9-SEC-02 scoped CONDITIONAL PASS and Phase9 development decision; no owner residual-risk acceptance.",
"classification": "CONDITIONAL_DEVELOPMENT",
"status": "CONDITIONALLY_DEFERRED"
},
{
"id": "CI-01",
"description": "Hosted MariaDB remains quarantined; current runner isolation/lifecycle is unverified and no hosted cases are qualified.",
"evidence": [
"docs/redesign/phase-9/p9-sec-02/CI_ASSESSMENT.md",
".gitea/workflows/mariadb-integration.yml"
],
"severity": "HIGH",
"acceptanceCriterion": "Hosted provider and infrastructure isolation evidence",
"requiredAction": "Provision dedicated nonproduction runner/daemon and native private service; prove merged HostConfig/egress/secret boundaries plus success/fail/cancel/crash cleanup, then reviewed enabling change and actual hosted execution. Do not bypass unconditional workflow guard.",
"blockingPhase": [
"HOSTED_CI_ACCEPTANCE",
"PRODUCTION",
"AUTOMATION: if hosted lane required"
],
"responsibleParty": "CI infrastructure operator",
"deferralAuthority": "P9-SEC-02 CI-M01 allows operator-blocked hosted CI while independent local qualification continues; does not qualify controller execution.",
"classification": "CONDITIONAL_DEVELOPMENT",
"status": "BLOCKED"
},
{
"id": "MYSQL-TLS-01",
"description": "MySqlConnector2.4.0 has the retained moderate GHSA-473q-m89c-ghf8 advisory; production TLS configuration was not inspected.",
"evidence": [
"docs/pre-phase10-ux/DEPENDENCY_AUDIT.md"
],
"severity": "MODERATE",
"acceptanceCriterion": "Current dependency/reachability disposition; no accepted residual security risk",
"requiredAction": "Qualify >=2.6.1 or the documented trusted-CA mitigation; verify actual affected TLS configuration and rerun MariaDB/RD21. Isolated loopback SslMode=None evidence is not production TLS acceptance.",
"blockingPhase": [
"PRODUCTION_SECURITY_RELEASE"
],
"responsibleParty": "Database/security release owner",
"deferralAuthority": null,
"classification": "PRODUCTION_ONLY",
"status": "BLOCKED"
},
{
"id": "OPS-01",
"description": "Production filesystem integrity, independent off-disk restore/rollback, deployed identity/cache/monitoring and recovery evidence remain unverified.",
"evidence": [
"docs/redesign/phase-9/p9-sec-02/ACCEPTANCE_ADDENDUM_2026-10-25.md"
],
"severity": "CRITICAL",
"acceptanceCriterion": "Independent infrastructure, backup, recovery and release acceptance",
"requiredAction": "Complete independent operator verification and release approval on authorised infrastructure; do not perform production repairs, DB operations or service/cache changes from this handoff.",
"blockingPhase": [
"PRODUCTION"
],
"responsibleParty": "Production operator / release owner",
"deferralAuthority": null,
"classification": "PRODUCTION_ONLY",
"status": "BLOCKED"
},
{
"id": "NATIVE-SEC-01",
"description": "Native parser/decoder fuzzing, whole-image review and applicable Windows memory enforcement remain outside the qualified bounded fixture evidence.",
"evidence": [
"docs/pre-phase10-ux/SECURITY_REVIEW.md"
],
"severity": "HIGH",
"acceptanceCriterion": "Secure bounded untrusted document processing",
"requiredAction": "Qualify native resource/isolation/security boundaries; retain page/file/time/child/container admission limits and privacy/retention cleanup.",
"blockingPhase": [
"PRODUCTION_UNTRUSTED_DOCUMENT_PROCESSING"
],
"responsibleParty": "Security / runtime owner",
"deferralAuthority": null,
"classification": "PRODUCTION_ONLY",
"status": "BLOCKED"
},
{
"id": "FUTURE-01",
"description": "Separate P5-13/C11, LinkedIn, Android, SQLite retirement, MariaDB upgrade, monitoring and country initiatives remain tracked future work.",
"evidence": [
"ROADMAP.md"
],
"severity": "INFORMATIONAL",
"acceptanceCriterion": "No silent backlog loss or unrelated implementation",
"requiredAction": "Retain initiative-specific entry decisions and dependencies; do not activate withheld features, remove compatibility or perform major upgrades during cleanup.",
"blockingPhase": [
"SEPARATE_AUTHORISED_WORKSTREAMS"
],
"responsibleParty": "Product / initiative owners",
"deferralAuthority": null,
"classification": "FUTURE_FEATURE",
"status": "OPEN"
},
{
"id": "AI-CONTEXT-01",
"description": "Generic strategy admission still requires legacy ProfileCvText; successful reviewed structured import alone does not establish that precondition.",
"evidence": ["AI_RESULTS.md", "JobTrackerApi/Services/StrategySnapshotService.cs", "JobTrackerApi/Controllers/ProfileCvController.Import.cs"],
"severity": "MODERATE",
"acceptanceCriterion": "Do not claim canonical-only import to generic AI compatibility without exercising it",
"requiredAction": "Assess canonical-only generic-AI source compatibility during Product Review. Preserve existing consent/source contracts; never silently fill legacy text from unreviewed uploads. The current successful smoke explicitly supplies separately authored fictional CV text.",
"blockingPhase": ["GENERIC_AI_FROM_CANONICAL_ONLY_PROFILE_CLAIM"],
"responsibleParty": "Product / AI integration owner",
"deferralAuthority": null,
"classification": "FUTURE_FEATURE",
"status": "OPEN"
},
{
"id": "AUTO-SCOPE-01",
"description": "The committed controller policy has stage entry/review approvals but no explicit protected candidate-scope checkpoint after an assessment-only Phase10 entry.",
"evidence": ["CONTRACT.md", "GATES.json", "CLEANUP_INVENTORY.md"],
"severity": "HIGH",
"acceptanceCriterion": "Concrete owner-approved candidate scope before cleanup implementation",
"requiredAction": "Bind a full entry approval to already-proven exact candidates, or use the controller's signed policy update to stop after assessment and require a protected candidate-specific-cleanup-scope decision before removal. Do not treat assessment-only approval or worker-written evidence as deletion authority.",
"blockingPhase": ["AUTOMATIC_PHASE-10_CLEANUP_IMPLEMENTATION"],
"responsibleParty": "Owner / AUTONOMOUS-ROADMAP-01 policy maintainer",
"deferralAuthority": null,
"classification": "MANDATORY_ENTRY",
"status": "BLOCKED"
},
{
"id": "GIT-RECOVERY-01",
"description": "The originally pending caa0c6a and5032ccb publication was recovered and independently read back.",
"evidence": [
"AUTHORITY.md"
],
"severity": "HIGH",
"acceptanceCriterion": "Clean directly verified remote baseline",
"requiredAction": "Reverify exact final local/remote HEAD after publishing the finished package; normal push only. GJ-00 was independently verified without modifying/merging it.",
"blockingPhase": [
"PHASE-10: final published baseline"
],
"responsibleParty": "Git integration owner",
"deferralAuthority": null,
"classification": "MANDATORY_ENTRY",
"status": "CLOSED"
}
]
}
@@ -0,0 +1,28 @@
# Proposed evidence adapter v1
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 |
|---|---|
| ACCEPTANCE.localCommit | Frozen application/test baseline; independently prove ancestry and application-source equality for the resolved final package commit. Never assume the capture-time remote observation is current |
| ACCEPTANCE.snapshotHead / verifiedRemoteCommit | Dated provenance only. Resolve final branch HEAD again, require clean local/remote equality, and bind the protected entry request to that actual hash |
| TEST_EVIDENCE native report/process/source artifacts | Recompute compressed-file hashes, decompress with a strict bound, parse native identities/counters and process exits. Source before/after must agree. Verify all expected ordinary discovery identities exactly once; retain separate lanes and original failures |
| GATES stage IDs / predecessor / manualApprovals | Projection of the existing seven stages05–11 and their protected approval identifiers. Do not insert this evidence package as a second executable queue or enable execution from it |
| BLOCKERS classification / blockingPhase | Scope-dependent entry, conditional development debt, production and future work. Neither a conditional result nor a closed unrelated blocker satisfies a mandatory candidate condition |
| requiredOwnerApprovals | Requests for external protected owner decisions, not records that approval occurred. This package contains no approval token, signature or private key |
| packageStatus READY | All required evidence/documentation is present and schema-consistent. It says nothing about approval to start Phase10 |
| phase10DevelopmentReadiness NO-GO / eligibleForAutomaticExecution false | Current authoritative entry decision. The controller must remain blocked pending actual protected approvals, candidate proof, publication and runtime/test-adapter qualification |
The controller's existing `result.schema.json` uses outcomes such as blocked/conditional/complete and blocker/approval/checkpoint fields. A valid handoff with this entry decision maps to **blocked** for Phase10; do not translate validator exit0 into `complete` or a satisfied stage requirement. No result payload can sign its own authority. The controller must independently verify real predecessor acceptance and perform its protected `phase10-development-entry` and stage-review flow.
The current manifest has no declared checkpoint deferrable gates. Human release debt and disabled optional CV AI have individual historical conditional-development authority, but importing that debt does not authorise automatic advancement. A policy change and any actual conditional-deferral must use the controller's owner-controlled signed request process. Required automated failures remain failures. Preserve actual S01 failures and isolated model rejections.
The controller snapshot also has no explicit protected candidate-scope checkpoint between an assessment-only entry and removal. The proposed GATES contract adds `candidate-specific-cleanup-scope` as a source-derived requirement, not as a claim that the live manifest already enforces it. Either the entry grant must already name proven exact candidates, or the operator must adopt a signed policy update that stops after assessment and requires that protected decision before cleanup implementation. This workstream cannot add that hook in the separate worktree. AUTO-SCOPE-01 stays BLOCKED.
## Portable evidence
Native test reports, process observations and source manifests are stored as individually compressed `.gz` files under `evidence/` with exact hashes in TEST_EVIDENCE.json. Native XML/JSON contents are preserved, not rewritten to improve results. The local raw `.artifacts/automation-handoff-01/` evidence remains separately available; large videos, traces, model weights and document corpora are not imported into this package. Visual review uses its separately indexed synthetic comparison package.
The package verifier only reads bounded local files/Git and optionally verifies the configured remote. It does not execute report commands, install dependencies, run a controller, inspect protected approval keys or grant readiness. Installed pinned Ajv/jsdom from the existing frontend dev dependency tree provide actual JSON Schema/XML validation; no dependency was added. External consumers may use their own qualified parsers and must not trust this model-writable verifier as their security boundary.
At consumption, require a clean authorised checkout containing the final published package, verify hashes/source/ancestry, and independently run the controller's actual qualified test adapter. Hold protected authority and expected policy/report hashes outside model-writable repository state. If the adapter, signatures, runtime boundary or report transport is unavailable, remain BLOCKED. Do not weaken gates, issue self-approval or edit another worktree as an integration shortcut.
@@ -0,0 +1,278 @@
{
"schemaVersion": 1,
"workstreamId": "AUTOMATION-HANDOFF-01",
"kind": "GATES",
"contractStatus": "PROPOSED_EVIDENCE_ADAPTER_NOT_EXECUTABLE_POLICY",
"controllerCommit": "c1663df44ca54eb1e6fad8facfcbc2d5eb5db8c8",
"controllerFiles": [
"automation/backlog.json",
"automation/schemas/backlog.schema.json",
"automation/schemas/result.schema.json",
"docs/automation/GATES.md"
],
"executionEnabledByThisPackage": false,
"externalApprovalPolicy": "Use existing protected RSA/SHA256 owner-SID/request-hash/expiry binding. Bind resolved final baseline, candidate scope, policy/prompt and artifact hashes. No signatures/tokens are created here; all current checkpoint deferrableGates are empty. Any policy update/conditional-deferral requires the controller signed process.",
"stages": [
{
"id": "PHASE-10",
"sequence": 5,
"requiredPredecessor": [
"AUTOMATION-HANDOFF-01 accepted entry and protected owner decision"
],
"requiredTestSuites": [
"Fresh applicable .NET, React, browser/contracts/import, MariaDB/RD21, Python/Docling, native PDF, type/lint/build/security/preservation lanes",
"Stage-specific requirements and independent source-requirements review"
],
"minimumEvidence": [
"Native nonempty reports and command exits; source/commit/image binding; expected test membership; zero hidden failures/skips/retries",
"Original failures retained; actual cleanup and old-data/route compatibility; human/provider/model results separate"
],
"requiredGitState": [
"Exact approved published baseline ancestry",
"Clean authorised feature checkout; normal push and independent exact remote HEAD readback",
"No main merge, force push or deployment"
],
"preservationChecks": [
"All57 original criteria retain status and scope; all33 old routes plus admin processing route",
"Canonical Career Profile, CV overrides/frozen versions, letters, applications, auth/tenant and RD21 contracts",
"Candidate-specific caller/old-data/rollback proof and unchanged PDF/font oracles"
],
"manualApprovals": [
"phase10-development-entry",
"stage-requirements-review",
"candidate-specific-cleanup-scope"
],
"permissibleConditionalDeferrals": [],
"blockingFindings": [
"Missing predecessor or protected approval",
"Unqualified runtime/test adapter or missing/wrong-source report",
"Unpublished commit; failed mandatory test; undispositioned affected compatibility or security finding",
"Missing protected candidate-scope decision before removal; assessment-only entry cannot authorise deletions"
]
},
{
"id": "PRODUCT-REVIEW-01",
"sequence": 6,
"requiredPredecessor": [
"PHASE-10"
],
"requiredTestSuites": [
"Fresh applicable .NET, React, browser/contracts/import, MariaDB/RD21, Python/Docling, native PDF, type/lint/build/security/preservation lanes",
"Stage-specific requirements and independent source-requirements review"
],
"minimumEvidence": [
"Native nonempty reports and command exits; source/commit/image binding; expected test membership; zero hidden failures/skips/retries",
"Original failures retained; actual cleanup and old-data/route compatibility; human/provider/model results separate"
],
"requiredGitState": [
"Exact approved published baseline ancestry",
"Clean authorised feature checkout; normal push and independent exact remote HEAD readback",
"No main merge, force push or deployment"
],
"preservationChecks": [
"All57 original criteria retain status and scope; all33 old routes plus admin processing route",
"Canonical Career Profile, CV overrides/frozen versions, letters, applications, auth/tenant and RD21 contracts",
"Candidate-specific caller/old-data/rollback proof and unchanged PDF/font oracles"
],
"manualApprovals": [
"product-ranked-scope",
"stage-requirements-review"
],
"permissibleConditionalDeferrals": [],
"blockingFindings": [
"Missing predecessor or protected approval",
"Unqualified runtime/test adapter or missing/wrong-source report",
"Unpublished commit; failed mandatory test; undispositioned affected compatibility or security finding"
]
},
{
"id": "INT-GLOBAL-JOBS-01",
"sequence": 7,
"requiredPredecessor": [
"PRODUCT-REVIEW-01"
],
"requiredTestSuites": [
"Fresh applicable .NET, React, browser/contracts/import, MariaDB/RD21, Python/Docling, native PDF, type/lint/build/security/preservation lanes",
"Stage-specific requirements and independent source-requirements review"
],
"minimumEvidence": [
"Native nonempty reports and command exits; source/commit/image binding; expected test membership; zero hidden failures/skips/retries",
"Original failures retained; actual cleanup and old-data/route compatibility; human/provider/model results separate"
],
"requiredGitState": [
"Exact approved published baseline ancestry",
"Clean authorised feature checkout; normal push and independent exact remote HEAD readback",
"No main merge, force push or deployment"
],
"preservationChecks": [
"All57 original criteria retain status and scope; all33 old routes plus admin processing route",
"Canonical Career Profile, CV overrides/frozen versions, letters, applications, auth/tenant and RD21 contracts",
"Candidate-specific caller/old-data/rollback proof and unchanged PDF/font oracles"
],
"manualApprovals": [
"global-jobs-implementation",
"product-ranked-scope",
"provider-licences-and-display-rights",
"stage-requirements-review"
],
"permissibleConditionalDeferrals": [],
"blockingFindings": [
"Missing predecessor or protected approval",
"Unqualified runtime/test adapter or missing/wrong-source report",
"Unpublished commit; failed mandatory test; undispositioned affected compatibility or security finding"
]
},
{
"id": "I18N-01",
"sequence": 8,
"requiredPredecessor": [
"INT-GLOBAL-JOBS-01"
],
"requiredTestSuites": [
"Fresh applicable .NET, React, browser/contracts/import, MariaDB/RD21, Python/Docling, native PDF, type/lint/build/security/preservation lanes",
"Stage-specific requirements and independent source-requirements review"
],
"minimumEvidence": [
"Native nonempty reports and command exits; source/commit/image binding; expected test membership; zero hidden failures/skips/retries",
"Original failures retained; actual cleanup and old-data/route compatibility; human/provider/model results separate"
],
"requiredGitState": [
"Exact approved published baseline ancestry",
"Clean authorised feature checkout; normal push and independent exact remote HEAD readback",
"No main merge, force push or deployment"
],
"preservationChecks": [
"All57 original criteria retain status and scope; all33 old routes plus admin processing route",
"Canonical Career Profile, CV overrides/frozen versions, letters, applications, auth/tenant and RD21 contracts",
"Candidate-specific caller/old-data/rollback proof and unchanged PDF/font oracles"
],
"manualApprovals": [
"human-linguistic-and-market-qualification",
"i18n-scope",
"stage-requirements-review"
],
"permissibleConditionalDeferrals": [],
"blockingFindings": [
"Missing predecessor or protected approval",
"Unqualified runtime/test adapter or missing/wrong-source report",
"Unpublished commit; failed mandatory test; undispositioned affected compatibility or security finding"
]
},
{
"id": "LAUNCH-SITE-01",
"sequence": 9,
"requiredPredecessor": [
"I18N-01"
],
"requiredTestSuites": [
"Fresh applicable .NET, React, browser/contracts/import, MariaDB/RD21, Python/Docling, native PDF, type/lint/build/security/preservation lanes",
"Stage-specific requirements and independent source-requirements review"
],
"minimumEvidence": [
"Native nonempty reports and command exits; source/commit/image binding; expected test membership; zero hidden failures/skips/retries",
"Original failures retained; actual cleanup and old-data/route compatibility; human/provider/model results separate"
],
"requiredGitState": [
"Exact approved published baseline ancestry",
"Clean authorised feature checkout; normal push and independent exact remote HEAD readback",
"No main merge, force push or deployment"
],
"preservationChecks": [
"All57 original criteria retain status and scope; all33 old routes plus admin processing route",
"Canonical Career Profile, CV overrides/frozen versions, letters, applications, auth/tenant and RD21 contracts",
"Candidate-specific caller/old-data/rollback proof and unchanged PDF/font oracles"
],
"manualApprovals": [
"homepage-design-selection",
"stage-requirements-review"
],
"permissibleConditionalDeferrals": [],
"blockingFindings": [
"Missing predecessor or protected approval",
"Unqualified runtime/test adapter or missing/wrong-source report",
"Unpublished commit; failed mandatory test; undispositioned affected compatibility or security finding"
]
},
{
"id": "SECURITY-AUDIT-01",
"sequence": 10,
"requiredPredecessor": [
"LAUNCH-SITE-01"
],
"requiredTestSuites": [
"Fresh applicable .NET, React, browser/contracts/import, MariaDB/RD21, Python/Docling, native PDF, type/lint/build/security/preservation lanes",
"Stage-specific requirements and independent source-requirements review"
],
"minimumEvidence": [
"Native nonempty reports and command exits; source/commit/image binding; expected test membership; zero hidden failures/skips/retries",
"Original failures retained; actual cleanup and old-data/route compatibility; human/provider/model results separate"
],
"requiredGitState": [
"Exact approved published baseline ancestry",
"Clean authorised feature checkout; normal push and independent exact remote HEAD readback",
"No main merge, force push or deployment"
],
"preservationChecks": [
"All57 original criteria retain status and scope; all33 old routes plus admin processing route",
"Canonical Career Profile, CV overrides/frozen versions, letters, applications, auth/tenant and RD21 contracts",
"Candidate-specific caller/old-data/rollback proof and unchanged PDF/font oracles"
],
"manualApprovals": [
"isolated-adversarial-targets",
"material-security-findings-disposition",
"security-test-environment-and-scope",
"stage-requirements-review"
],
"permissibleConditionalDeferrals": [],
"blockingFindings": [
"Missing predecessor or protected approval",
"Unqualified runtime/test adapter or missing/wrong-source report",
"Unpublished commit; failed mandatory test; undispositioned affected compatibility or security finding"
]
},
{
"id": "RELEASE-READINESS-01",
"sequence": 11,
"requiredPredecessor": [
"SECURITY-AUDIT-01"
],
"requiredTestSuites": [
"Fresh applicable .NET, React, browser/contracts/import, MariaDB/RD21, Python/Docling, native PDF, type/lint/build/security/preservation lanes",
"Stage-specific requirements and independent source-requirements review"
],
"minimumEvidence": [
"Native nonempty reports and command exits; source/commit/image binding; expected test membership; zero hidden failures/skips/retries",
"Original failures retained; actual cleanup and old-data/route compatibility; human/provider/model results separate"
],
"requiredGitState": [
"Exact approved published baseline ancestry",
"Clean authorised feature checkout; normal push and independent exact remote HEAD readback",
"No main merge, force push or deployment"
],
"preservationChecks": [
"All57 original criteria retain status and scope; all33 old routes plus admin processing route",
"Canonical Career Profile, CV overrides/frozen versions, letters, applications, auth/tenant and RD21 contracts",
"Candidate-specific caller/old-data/rollback proof and unchanged PDF/font oracles"
],
"manualApprovals": [
"release-evidence-review",
"stage-requirements-review"
],
"permissibleConditionalDeferrals": [],
"blockingFindings": [
"Missing predecessor or protected approval",
"Unqualified runtime/test adapter or missing/wrong-source report",
"Unpublished commit; failed mandatory test; undispositioned affected compatibility or security finding"
]
}
],
"separateInitiatives": [
"P5-13/C11 AI qualification and model assessment",
"LinkedIn feasibility",
"Android application",
"SQLite retirement",
"MariaDB upgrade strategy",
"Monitoring and operational improvements",
"Future country integrations"
]
}
@@ -0,0 +1,119 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "urn:jobjakt:pre-phase10-evidence:1",
"title": "Proposed pre-Phase10 evidence contract v1; not an execution or approval policy",
"oneOf": [
{ "$ref": "#/definitions/acceptance" },
{ "$ref": "#/definitions/gates" },
{ "$ref": "#/definitions/blockers" },
{ "$ref": "#/definitions/evidence" }
],
"definitions": {
"sha": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
"commit": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
"strings": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
"commonVersion": { "const": 1 },
"workstream": { "const": "AUTOMATION-HANDOFF-01" },
"status": { "enum": ["GO", "CONDITIONAL GO", "NO-GO", "PASS", "PARTIAL", "FAIL", "BLOCKED", "NOT TESTED", "CONDITIONAL", "VERIFIED", "READY", "INCOMPLETE"] },
"counts": {
"type": "object", "additionalProperties": false,
"required": ["passed", "failed", "skipped", "retries", "unexecuted"],
"properties": {
"passed": { "type": "integer", "minimum": 0 }, "failed": { "type": "integer", "minimum": 0 },
"skipped": { "type": "integer", "minimum": 0 }, "retries": { "type": "integer", "minimum": 0 },
"unexecuted": { "type": "integer", "minimum": 0 }
}
},
"verdict": {
"type": "object", "additionalProperties": false, "required": ["criterion", "status", "evidence", "limits"],
"properties": { "criterion": { "type": "string" }, "status": { "$ref": "#/definitions/status" }, "evidence": { "$ref": "#/definitions/strings" }, "limits": { "type": "string", "minLength": 1 } }
},
"approval": {
"type": "object", "additionalProperties": false, "required": ["id", "state", "authority"],
"properties": { "id": { "type": "string" }, "state": { "const": "NOT_PROVIDED_TO_HANDOFF" }, "authority": { "const": "EXTERNAL_PROTECTED_CONTROLLER_APPROVAL" } }
},
"acceptance": {
"type": "object", "additionalProperties": false,
"required": ["schemaVersion", "workstreamId", "kind", "authority", "sourceBranch", "localCommit", "localCommitMeaning", "snapshotHead", "verifiedRemoteCommit", "remoteObservation", "worktreeCleanliness", "acceptanceStatus", "entryGateStatus", "phase10DevelopmentReadiness", "eligibleForAutomaticExecution", "packageStatus", "verdicts", "testEvidenceReferences", "unresolvedBlockerIds", "permissibleDeferrals", "requiredOwnerApprovals", "generatedUtc", "recommendation"],
"properties": {
"schemaVersion": { "$ref": "#/definitions/commonVersion" }, "workstreamId": { "$ref": "#/definitions/workstream" }, "kind": { "const": "ACCEPTANCE" },
"authority": { "const": "EVIDENCE_ONLY" }, "sourceBranch": { "const": "test/pre-phase10-ux-acceptance" },
"localCommit": { "$ref": "#/definitions/commit" }, "localCommitMeaning": { "const": "FROZEN_TESTED_APPLICATION_BASELINE" },
"snapshotHead": { "$ref": "#/definitions/commit" }, "verifiedRemoteCommit": { "anyOf": [{ "$ref": "#/definitions/commit" }, { "type": "null" }] },
"remoteObservation": { "type": "string", "minLength": 1 },
"worktreeCleanliness": { "type": "object", "additionalProperties": false, "required": ["atSourceBaseline", "atPackageGeneration", "requiredAtConsumption"], "properties": { "atSourceBaseline": { "const": "CLEAN" }, "atPackageGeneration": { "enum": ["CLEAN", "HANDOFF_FILES_ONLY"] }, "requiredAtConsumption": { "const": "CLEAN" } } },
"acceptanceStatus": { "const": "CONDITIONAL GO" }, "entryGateStatus": { "const": "BLOCKED" },
"phase10DevelopmentReadiness": { "const": "NO-GO" }, "eligibleForAutomaticExecution": { "const": false },
"packageStatus": { "enum": ["READY", "INCOMPLETE"] }, "verdicts": { "type": "array", "minItems": 12, "items": { "$ref": "#/definitions/verdict" } },
"testEvidenceReferences": { "$ref": "#/definitions/strings" }, "unresolvedBlockerIds": { "$ref": "#/definitions/strings" },
"permissibleDeferrals": { "$ref": "#/definitions/strings" }, "requiredOwnerApprovals": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/approval" } },
"generatedUtc": { "type": "string", "format": "date-time" }, "recommendation": { "type": "string", "minLength": 1 }
}
},
"stage": {
"type": "object", "additionalProperties": false,
"required": ["id", "sequence", "requiredPredecessor", "requiredTestSuites", "minimumEvidence", "requiredGitState", "preservationChecks", "manualApprovals", "permissibleConditionalDeferrals", "blockingFindings"],
"properties": {
"id": { "type": "string" }, "sequence": { "type": "integer", "minimum": 5, "maximum": 11 },
"requiredPredecessor": { "$ref": "#/definitions/strings" }, "requiredTestSuites": { "$ref": "#/definitions/strings" },
"minimumEvidence": { "$ref": "#/definitions/strings" }, "requiredGitState": { "$ref": "#/definitions/strings" },
"preservationChecks": { "$ref": "#/definitions/strings" }, "manualApprovals": { "$ref": "#/definitions/strings" },
"permissibleConditionalDeferrals": { "$ref": "#/definitions/strings" }, "blockingFindings": { "$ref": "#/definitions/strings" }
}
},
"gates": {
"type": "object", "additionalProperties": false,
"required": ["schemaVersion", "workstreamId", "kind", "contractStatus", "controllerCommit", "controllerFiles", "executionEnabledByThisPackage", "externalApprovalPolicy", "stages", "separateInitiatives"],
"properties": {
"schemaVersion": { "$ref": "#/definitions/commonVersion" }, "workstreamId": { "$ref": "#/definitions/workstream" }, "kind": { "const": "GATES" },
"contractStatus": { "const": "PROPOSED_EVIDENCE_ADAPTER_NOT_EXECUTABLE_POLICY" }, "controllerCommit": { "$ref": "#/definitions/commit" },
"controllerFiles": { "$ref": "#/definitions/strings" }, "executionEnabledByThisPackage": { "const": false },
"externalApprovalPolicy": { "type": "string", "minLength": 1 }, "stages": { "type": "array", "minItems": 7, "maxItems": 7, "items": { "$ref": "#/definitions/stage" } },
"separateInitiatives": { "$ref": "#/definitions/strings" }
}
},
"blocker": {
"type": "object", "additionalProperties": false,
"required": ["id", "description", "evidence", "severity", "acceptanceCriterion", "requiredAction", "blockingPhase", "responsibleParty", "deferralAuthority", "classification", "status"],
"properties": {
"id": { "type": "string", "pattern": "^[A-Z][A-Z0-9-]+$" }, "description": { "type": "string", "minLength": 1 }, "evidence": { "$ref": "#/definitions/strings" },
"severity": { "enum": ["CRITICAL", "HIGH", "MODERATE", "LOW", "INFORMATIONAL"] }, "acceptanceCriterion": { "type": "string" },
"requiredAction": { "type": "string", "minLength": 1 }, "blockingPhase": { "$ref": "#/definitions/strings" }, "responsibleParty": { "type": "string", "minLength": 1 },
"deferralAuthority": { "anyOf": [{ "type": "string", "minLength": 1 }, { "type": "null" }] },
"classification": { "enum": ["MANDATORY_ENTRY", "CONDITIONAL_DEVELOPMENT", "PRODUCTION_ONLY", "FUTURE_FEATURE"] },
"status": { "enum": ["OPEN", "BLOCKED", "CONDITIONALLY_DEFERRED", "CLOSED"] }
}
},
"blockers": {
"type": "object", "additionalProperties": false, "required": ["schemaVersion", "workstreamId", "kind", "items"],
"properties": { "schemaVersion": { "$ref": "#/definitions/commonVersion" }, "workstreamId": { "$ref": "#/definitions/workstream" }, "kind": { "const": "BLOCKERS" }, "items": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/blocker" } } }
},
"artifact": {
"type": "object", "additionalProperties": false, "required": ["id", "path", "sha256", "description"],
"properties": { "id": { "type": "string" }, "path": { "type": "string", "minLength": 1 }, "sha256": { "$ref": "#/definitions/sha" }, "description": { "type": "string" } }
},
"report": {
"type": "object", "additionalProperties": false,
"required": ["id", "bindingScope", "sourceCommit", "executedFromCommit", "status", "exitCode", "artifactId", "processArtifactId", "processPointer", "nativeFormat", "counts", "membershipSha256", "limits"],
"properties": {
"id": { "type": "string" }, "bindingScope": { "enum": ["APPLICATION", "HANDOFF_VERIFIER"] }, "sourceCommit": { "$ref": "#/definitions/commit" }, "executedFromCommit": { "$ref": "#/definitions/commit" },
"status": { "enum": ["PASS", "PARTIAL", "FAIL", "BLOCKED", "METADATA"] }, "exitCode": { "type": "integer" },
"artifactId": { "type": "string" }, "processArtifactId": { "type": "string" }, "processPointer": { "type": "string", "pattern": "^/" },
"nativeFormat": { "enum": ["trx", "junit", "jest", "playwright", "pytest", "unittest", "node-test", null] },
"counts": { "$ref": "#/definitions/counts" }, "membershipSha256": { "$ref": "#/definitions/sha" }, "limits": { "type": "string", "minLength": 1 }
}
},
"evidence": {
"type": "object", "additionalProperties": false,
"required": ["schemaVersion", "workstreamId", "kind", "testedCommit", "verifierCommit", "verifierSourceArtifactId", "sourceManifestArtifactId", "sourceAfterArtifactId", "ordinaryDiscoveryArtifactId", "ordinaryReportIds", "artifacts", "reports", "historicalEvidence", "environment", "notRun", "generatedUtc"],
"properties": {
"schemaVersion": { "$ref": "#/definitions/commonVersion" }, "workstreamId": { "$ref": "#/definitions/workstream" }, "kind": { "const": "TEST_EVIDENCE" },
"testedCommit": { "$ref": "#/definitions/commit" }, "sourceManifestArtifactId": { "type": "string" }, "sourceAfterArtifactId": { "type": "string" },
"verifierCommit": { "$ref": "#/definitions/commit" }, "verifierSourceArtifactId": { "type": "string" },
"ordinaryDiscoveryArtifactId": { "type": "string" }, "ordinaryReportIds": { "$ref": "#/definitions/strings" },
"artifacts": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/artifact" } }, "reports": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/report" } },
"historicalEvidence": { "$ref": "#/definitions/strings" }, "environment": { "$ref": "#/definitions/strings" }, "notRun": { "$ref": "#/definitions/strings" }, "generatedUtc": { "type": "string", "format": "date-time" }
}
}
}
}
@@ -1,6 +1,6 @@
# Prioritised owner acceptance
All human rows are **NOT TESTED** until the owner supplies actual evidence through the protected approval mechanism. Do not commit approval tokens/signatures or personal CVs. Use fictional data and http://jobbjakt.local; admin scenarios require an existing Admin account, AI scenarios require consent/entitlement. Capture date, exact source/image, device/OS/browser/assistive technology, steps, result and synthetic-only screenshot/PDF where appropriate. Never include passwords, cookies or personal documents.
All human rows are **NOT TESTED** until the owner supplies actual evidence through the protected approval mechanism. Do not commit approval tokens/signatures or personal CVs. Use fictional data and http://jobbjakt.local; admin scenarios require an existing Admin account, AI scenarios require consent/entitlement and the workflow's existing CV-text precondition. For revised spacing, use a new CV or explicitly Apply the updated design v2; historical v1 designs remain unchanged. Capture date, exact source/image, device/OS/browser/assistive technology, steps, result and synthetic-only screenshot/PDF where appropriate. Never include passwords, cookies or personal documents.
| ID / priority | Workflow and preconditions | Steps and expected pass criterion | Evidence and gate |
|---|---|---|---|
@@ -0,0 +1,25 @@
# Exact Phase10 assessment baseline
Application baseline: **5032ccb41514807c8ca3008c24ca6326be1170de**, branch **test/pre-phase10-ux-acceptance**, remote origin `https://git.cesnimda.uk/cesnimda/jobtrackingapp.git`. It contains CV-UX4a0af62, CV-QA4a8c907, A–D3255da9 and the structured-strategy correctioncaa0c6a. All four ancestry checks were rerun successfully in this workstream. Do not base cleanup on main.
The handoff checkpoint8804231be4e7c309a62d9d6e7d07c9ae0809bc3c was normally pushed and independently read back. Later handoff commits must also be published and verified. The **final integration ref is the resolved clean published HEAD containing this package**; obtain its exact hash with Git and bind the protected owner decision to it. A file cannot contain its own Git commit hash. ACCEPTANCE.json therefore distinguishes the frozen tested application baseline, capture-time HEAD/remote observation and required current consumption checks. Documentation-only descendants are bounded to `docs/automation/handoffs/pre-phase10/`; application or existing-test changes invalidate that shortcut and require fresh affected evidence.
The separate controller snapshot is c1663df44ca54eb1e6fad8facfcbc2d5eb5db8c8, based on5032ccb. Its branch is not modified here. The operator must use its existing signed policy-update/configure process to adopt the final approved integration baseline/context artifacts **before real execution**, qualify its runtime/test adapter and obtain the required entry approval. Do not edit its policy silently or run another controller. No baseline replacement after started work is authorised by this package.
GJ-00 planning local/remote0ec9f98e6ed4a646fc5e017d8eb6fade07653f37 is separately verified. Relative to5032ccb, the integration and planning branches have18 and2 unique commits respectively. Neither is a safe blind replacement for the other. No merge/cherry-pick was performed. Future authorised integration must reconcile the planning documents against the later accepted application source.
## Preservation contract
Retain all57 original capability groups and their existing56PARTIAL/oneDEFERRED qualification states. Preserve the33 original routes plus `/admin/processing` (34 current source routes), including legacy aliases. Route membership is only a source check, not full behavior acceptance. Keep the original criteria rather than promoting them from new unit/browser results.
Career Profile stays canonical; null-as-inherit overrides, explicit blank flags, per-CV customisation, frozen/submitted packages, letter/application versions, tenant/owner filters, durable operation state, transactional profile application and RD21 expected-version/rollback/retry contracts remain active. Do not reinterpret creation-intent idempotency as proven: the earlier provider observation explicitly records repeated creation bodies as non-idempotent.
Keep design-version1 rendering for existing/historical CVs; v2 is explicit. Keep old-run import review/accept DTOs and routes. Keep SQLite compatibility/migrations pending separately approved retirement; no MariaDB major upgrade. Preserve licensed fonts, unchanged original PDF goldens and all recorded S01 failures. Keep disabled C11/P5-13/model interpretation withheld.
## Scope and compatibility checks
[CLEANUP_INVENTORY.md](CLEANUP_INVENTORY.md) contains concrete current consumers and risks. **No product removal is approved or proven unused.** The safe next activity is candidate assessment and owner review; implementation needs the independent entry decision. Each proposed candidate must name exact symbols/routes/replacement/consumers, old-data and dynamic-reference risks, affected preservation IDs, rollback and focused proof. Then run applicable full backend/frontend/browser/contracts/import/MariaDB/RD21/Python/security/native Windows/Linux PDF/type/lint/build checks with native identities and exits.
The current native/Node/browser producer commands and source/process identities are retained in the portable TEST_EVIDENCE package. Regenerate isolated fixture databases, never target jobtracker_dev or production for destructive testing. Development runtime application commit remainscaa0c6a, which is source-equivalent to the application portions of5032ccb and these documentation-only descendants. It was not replaced merely to change a displayed documentation commit.
Production remains NO-GO; physical-device, human/AT/language/print, affected S01 disposition, real CV semantic quality, hosted CI/runtime isolation, native/image review and production operator qualifications remain separately tracked. None of these histories is deleted or accepted on the owner's behalf.
@@ -37,4 +37,6 @@ The sole active app-inventory chat using this cwd is this task; the separate act
The existing controller's result/backlog schemas are not acceptance-package schemas. Use a separately versioned **proposed evidence contract**, with explicit mapping to its protected signed entry/deferral/review gates, not a competing executable queue. Do not copy/configure its runtime or fabricate signatures.
The initial commit normalised the copied request's CRLF bytes. Scoped .gitattributes now preserves the original request and hash-indexed package bytes exactly across checkouts; no instruction or acceptance criterion is changed. Native evidence is compressed individually for portable verification rather than relying on ignored files in another worktree.
Existing Phase9 authority allows genuinely unfinished human checks as release debt and individually scoped conditional development; it does not grant broad Phase10 entry or accept unresolved security risks. Exact candidate approval and unused-caller proof remain mandatory. This handoff can be READY as evidence while Phase10 remains BLOCKED.
@@ -0,0 +1,14 @@
# Retained failures and recovery
Failed attempts are not deleted, counted as passing tests or hidden by retries.
| Attempt | Actual failure | Correction / boundary |
|---|---|---|
| post-01 | Temporary proxy exited before any browser case; immediate port lookup raised KeyError | A separate reproduction retained nginx's exact `mkdir /var/cache/nginx/fastcgi_temp: Read-only file system` error. The one-off fixture omitted FastCGI/uwsgi/scgi paths. post-02 points all temporary paths into the existing bounded `/tmp` mount and checks startup state. Root stays read-only; no application/configuration or test assertion changed |
| proxy-reproduction-02 | Expected diagnostic exit1 before tests | Exact logs/state and owned-resource cleanup retained; not a product failure or extra test case |
| post-02 Linux build | The runner incorrectly supplied an output outside `.artifacts/cv-ux-01`; existing helper rejected it before build | linux-post-03 resumes only the unfinished Linux/audit work under a fresh allowed child. The helper guard was preserved; already passing import/MariaDB/Windows lanes were not rerun or double-counted |
| Revised S01 oracle | Windows8/36 and the separately recorded Linux result; original2mm coordinate threshold unchanged | Content, name wrapping, sidebar raster and visible-ink diagnostics are distinct evidence. No golden/tolerance change or aesthetic self-approval |
| Isolated real CV-model probe | All3 synthetic responses rejected with ValueError | No model/validator replacement or activation. Compatibility/semantic accuracy remains BLOCKED; process exit0 means the measurement completed |
| Initial handoff-validator RED | New tests failed because implementation module was absent | Native parser/hash/approval tests implemented and rerun; final unit/integration results supersede overlapping focused runs, without adding them |
Raw evidence is under `.artifacts/automation-handoff-01/`, with portable native reports/process/source artifacts in TEST_EVIDENCE.json. The original broad regression ran once to completion with all four groups passing and source unchanged; it is not the previous workstream's composite recovery result. Transient provider_unavailable history on the fresh successful strategy operation is retained in the local-worker result; it is not described as first-attempt success.
@@ -0,0 +1,26 @@
# Ready-to-run conditions
**Phase10 and real autonomous execution remain BLOCKED. This package does not authorise either.** A structurally READY evidence package can truthfully contain a NO-GO entry decision.
Before the separate controller starts anything, its operator must independently:
1. Resolve the final `test/pre-phase10-ux-acceptance` HEAD from Gitea and local Git; require exact equality, clean checkout and ancestry of5032ccb. Do not assume main contains the redesign. The package's localCommit is the frozen application baseline; its snapshotHead/remote observation are dated evidence, not self-referential claims about the later package commit.
2. Validate HANDOFF_SCHEMA.json and every indexed compressed native report/process/source artifact. Recompute hashes, native counters and identities, reject absent/unexecuted/failing/skipped/retried required cases, and check commit/source/image correspondence. Documentation-only descendants are explicitly bounded; any application/test change requires affected fresh qualification. Package metadata is not its own authority.
3. Obtain a protected `phase10-development-entry` approval bound to the **resolved final published commit**, exact candidate scope, current policy/prompt and artifact hashes. No approval was supplied to this handoff, and none is generated. A worker-written GO or signed-looking JSON is not approval.
4. Establish concrete unused caller/route/old-data proof, affected57-group/34-route preservation, rollback and candidate tests before any removal. Current inventory has **no approved removal**. Retain legacy CV design/run behavior, aliases, revision/persistence contracts, SQLite and migrations.
5. Resolve affected S01/rendering acceptance or obtain an explicit protected applicability decision supported by unchanged original failures and real visual evidence. Do not silently reinterpret a required automated failure as a deferral. Complete applicable human checks; existing release-only debt does not independently forbid unrelated assessment, but also does not grant implementation authority.
6. Qualify the separate controller's native sandbox/host/process/secret/read/egress/control-plane boundary and install/approve the actual stage composite test adapter. Its synthetic48+6 test evidence and static queue validity do not qualify real roadmap execution. This workstream did not modify or run it.
7. Reconcile individual conditional debt with the existing signed policy. Current checkpoint deferrableGates arrays are empty. Any future policy/conditional-deferral requires the controller's protected approval flow and cannot override a mandatory automated requirement. Keep hosted CI BLOCKED until actual isolated execution and cleanup proof; keep CV AI disabled and production NO-GO.
Local package checks:
```powershell
rtk proxy node --test docs/automation/handoffs/pre-phase10/verify.test.mjs
rtk proxy node docs/automation/handoffs/pre-phase10/verify.mjs
rtk proxy node docs/automation/handoffs/pre-phase10/verify.mjs --verify-remote
rtk proxy node docs/automation/handoffs/pre-phase10/verify.mjs --require-ready
```
Normal validation exit0 means evidence integrity is consistent; it prints Phase10 BLOCKED. `--require-ready` deliberately exits3 for this unapproved snapshot. This verifier does not run tests, launch a queue, validate/issue owner signatures or modify the controller. The controller must perform its own protected checks and fresh candidate verification. If it lacks these capabilities, stop rather than trusting a repository status.
No protected owner key/token, credentials, model cache or production endpoint belongs in this package. The separate worktree was inspected through committed Git objects only. No second orchestrator, scheduled automation, main merge, deployment or Phase10 implementation was started.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import zlib from 'node:zlib';
import { fileURLToPath } from 'node:url';
import { validatePackage } from './verify.mjs';
const directory = path.dirname(fileURLToPath(import.meta.url));
const artifactRoot = path.resolve(directory, '../../../../.artifacts');
const hash = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
function shadow(change) {
const temporary = fs.mkdtempSync(path.join(artifactRoot, 'handoff-verify-'));
try {
fs.cpSync(directory, temporary, { recursive: true });
change(temporary);
assert.throws(() => validatePackage(temporary));
} finally {
const resolved = path.resolve(temporary);
assert.ok(resolved.startsWith(artifactRoot + path.sep) && path.basename(resolved).startsWith('handoff-verify-'));
fs.rmSync(resolved, { recursive: true });
}
}
function json(directory, name, change) {
const file = path.join(directory, name + '.json');
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
change(value);
fs.writeFileSync(file, JSON.stringify(value));
}
test('complete evidence validates while Phase10 remains blocked', () => {
const result = validatePackage();
assert.equal(result.integrity, 'PASS');
assert.equal(result.phase10, 'BLOCKED');
assert.equal(result.eligible, false);
});
test('worker-written GO and approval-shaped data cannot advance', () => shadow(base => {
json(base, 'ACCEPTANCE', value => { value.phase10DevelopmentReadiness = 'GO'; value.eligibleForAutomaticExecution = true; value.ownerSignature = 'invented'; });
}));
test('report from a different commit is rejected', () => shadow(base => {
json(base, 'TEST_EVIDENCE', value => { value.reports[0].sourceCommit = '0'.repeat(40); });
}));
test('missing native artifact is rejected', () => shadow(base => {
const index = JSON.parse(fs.readFileSync(path.join(base, 'TEST_EVIDENCE.json'), 'utf8'));
const report = index.reports.find(item => item.nativeFormat);
fs.unlinkSync(path.join(base, index.artifacts.find(item => item.id === report.artifactId).path));
}));
test('changed native bytes are rejected', () => shadow(base => {
const index = JSON.parse(fs.readFileSync(path.join(base, 'TEST_EVIDENCE.json'), 'utf8'));
const report = index.reports.find(item => item.nativeFormat);
fs.appendFileSync(path.join(base, index.artifacts.find(item => item.id === report.artifactId).path), 'changed');
}));
test('rewriting a process hash cannot conceal its nonzero native exit', () => shadow(base => {
json(base, 'TEST_EVIDENCE', index => {
const report = index.reports.find(item => item.nativeFormat);
const artifact = index.artifacts.find(item => item.id === report.processArtifactId);
const file = path.join(base, artifact.path);
const data = JSON.parse(zlib.gunzipSync(fs.readFileSync(file)).toString('utf8'));
const keys = report.processPointer.slice(1).split('/');
let parent = data;
for (const key of keys.slice(0, -1)) parent = parent[key];
parent[keys.at(-1)] = 17;
const bytes = zlib.gzipSync(JSON.stringify(data));
fs.writeFileSync(file, bytes); artifact.sha256 = hash(bytes);
});
}));
test('omitting a source file from both manifests cannot claim a complete freeze', () => 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'));
delete data[Object.keys(data)[0]];
const bytes = zlib.gzipSync(JSON.stringify(data));
fs.writeFileSync(file, bytes); artifact.sha256 = hash(bytes);
}
});
}));
test('artifact paths cannot escape the imported package', () => shadow(base => {
json(base, 'TEST_EVIDENCE', index => { index.artifacts[0].path = '../../outside'; });
}));
test('an imported schema cannot weaken the trusted schema', () => shadow(base => {
fs.writeFileSync(path.join(base, 'HANDOFF_SCHEMA.json'), '{}');
}));
@@ -0,0 +1,206 @@
// Evidence validation only. Protected approvals and execution belong to the separate controller.
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import zlib from 'node:zlib';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
const directory = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(directory, '../../../..');
const dependencies = createRequire(path.join(root, 'job-tracker-ui/package.json'));
const Ajv = dependencies('ajv');
const { JSDOM } = dependencies('jsdom');
const digest = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
const demand = (condition, message) => { if (!condition) throw new Error(message); };
export function verifyBytes(bytes, expected) {
demand(Buffer.isBuffer(bytes) && /^[a-f0-9]{64}$/.test(expected) && digest(bytes) === expected, 'Missing or changed evidence');
}
export function measureNative(format, text) {
demand(Buffer.byteLength(text, 'utf8') <= 8 * 1024 * 1024, 'Native report exceeds its input bound');
const result = { passed: 0, failed: 0, skipped: 0, retries: 0, unexecuted: 0, names: [] };
if (format === 'trx') {
demand(!/<!DOCTYPE|<!ENTITY/i.test(text) && (text.match(/</g)?.length ?? 0) < 100000, 'XML declarations or excessive structure are not accepted');
const document = new JSDOM(text, { contentType: 'text/xml' }).window.document;
const counters = document.getElementsByTagNameNS('*', 'Counters')[0];
demand(counters, 'Missing native TRX counters');
for (const item of document.getElementsByTagNameNS('*', 'UnitTestResult')) {
result.names.push(item.getAttribute('testName'));
const outcome = item.getAttribute('outcome');
if (outcome === 'Passed') result.passed++;
else if (outcome === 'NotExecuted') result.skipped++;
else result.failed++;
}
demand(Number(counters.getAttribute('total')) === result.names.length && Number(counters.getAttribute('passed')) === result.passed && Number(counters.getAttribute('failed')) === result.failed, 'TRX membership/counters disagree');
} else if (format === 'junit') {
demand(!/<!DOCTYPE|<!ENTITY/i.test(text) && (text.match(/</g)?.length ?? 0) < 100000, 'XML declarations or excessive structure are not accepted');
const document = new JSDOM(text, { contentType: 'text/xml' }).window.document;
for (const item of document.getElementsByTagName('testcase')) {
result.names.push(`${item.getAttribute('classname')}::${item.getAttribute('name')}`);
if (item.getElementsByTagName('skipped').length) result.skipped++;
else if (item.getElementsByTagName('failure').length || item.getElementsByTagName('error').length) result.failed++;
else result.passed++;
}
} else if (format === 'jest') {
const report = JSON.parse(text);
for (const suite of report.testResults ?? []) for (const item of suite.assertionResults ?? []) {
const name = suite.name.replaceAll('\\', '/');
const marker = name.lastIndexOf('/job-tracker-ui/');
result.names.push(`${marker < 0 ? name : name.slice(marker + 1)}::${item.fullName}`);
if (item.status === 'passed') result.passed++;
else if (['pending', 'skipped', 'todo', 'disabled'].includes(item.status)) result.skipped++;
else result.failed++;
}
demand(report.numTotalTests === result.names.length && report.numPassedTests === result.passed && report.numFailedTests === result.failed && report.numPendingTests === result.skipped && !report.numRuntimeErrorTestSuites, 'Jest membership/counters disagree');
} else if (format === 'playwright') {
const visit = suite => {
for (const spec of suite.specs ?? []) for (const item of spec.tests ?? []) {
result.names.push(`${spec.id}::${item.projectName}`);
const attempts = item.results ?? [];
result.retries += Math.max(0, attempts.length - 1);
if (!attempts.length) result.unexecuted++;
else if (attempts.at(-1).status === 'skipped') result.skipped++;
else if (attempts.at(-1).status === 'passed' && item.expectedStatus !== 'failed' && item.status === 'expected') result.passed++;
else result.failed++;
if (attempts.some(attempt => (attempt.retry ?? 0) > 0)) result.retries = Math.max(1, result.retries);
}
for (const child of suite.suites ?? []) visit(child);
};
const report = JSON.parse(text);
demand(!(report.errors?.length), 'Browser report contains execution errors');
for (const suite of report.suites ?? []) visit(suite);
} else if (format === 'pytest') {
result.passed = Number(text.match(/\b(\d+) passed\b/)?.[1] ?? 0);
result.failed = Number(text.match(/\b(\d+) failed\b/)?.[1] ?? 0);
result.skipped = Number(text.match(/\b(\d+) skipped\b/)?.[1] ?? 0);
} else if (format === 'unittest') {
const count = Number(text.match(/Ran (\d+) tests? in/)?.[1] ?? 0);
if (/^OK\s*$/m.test(text)) result.passed = count;
else result.failed = count || 1;
result.skipped = Number(text.match(/skipped=(\d+)/)?.[1] ?? 0);
result.names = [...text.matchAll(/^(test\S+ \([^\n]+\)) \.\.\. /gm)].map(match => match[1]);
} else if (format === 'node-test') {
result.passed = Number(text.match(/(?:#|ℹ) pass (\d+)/u)?.[1] ?? 0);
result.failed = Number(text.match(/(?:#|ℹ) fail (\d+)/u)?.[1] ?? 0);
result.skipped = Number(text.match(/(?:#|ℹ) skipped (\d+)/u)?.[1] ?? 0);
result.names = [...text.matchAll(/^(?:✔|✖|ok \d+ -|not ok \d+ -) (.+?)(?: \([\d.]+ms\))?$/gmu)].map(match => match[1]);
} else throw new Error('Unknown native report format');
result.names.sort();
result.membershipSha256 = digest(Buffer.from(JSON.stringify(result.names)));
return result;
}
export function assertPass(result, exitCode) {
demand(exitCode === 0 && result.passed > 0 && !result.failed && !result.skipped && !result.retries && !result.unexecuted, 'Native result cannot qualify as PASS');
}
export function requireProtectedEntry() {
return { eligible: false, reason: 'External protected phase10-development-entry, candidate scope, qualified runtime and test adapter are required. This evidence validator grants no authority.' };
}
function readSafe(relative, maximum = 64 * 1024 * 1024, base = directory) {
demand(typeof relative === 'string' && !path.isAbsolute(relative) && !relative.split(/[\\/]/).includes('..'), 'Unsafe artifact path');
const file = path.resolve(base, relative);
demand(fs.realpathSync(file).startsWith(fs.realpathSync(base) + path.sep), 'Artifact escapes package');
const stat = fs.statSync(file);
demand(stat.isFile() && stat.size <= maximum, 'Missing or excessive artifact');
return fs.readFileSync(file);
}
export function validatePackage(base = directory) {
const readPackage = relative => readSafe(relative, 8 * 1024 * 1024, base);
// The schema is trusted verifier source, never replaced by an imported package's schema.
const schema = JSON.parse(readSafe('HANDOFF_SCHEMA.json'));
verifyBytes(readPackage('HANDOFF_SCHEMA.json'), digest(readSafe('HANDOFF_SCHEMA.json')));
const ajv = new Ajv({ allErrors: true, jsonPointers: true });
const validate = ajv.compile(schema);
const documents = {};
for (const name of ['ACCEPTANCE', 'GATES', 'BLOCKERS', 'TEST_EVIDENCE']) {
const document = JSON.parse(readPackage(name + '.json'));
demand(validate(document), `Invalid ${name}: ${ajv.errorsText(validate.errors)}`);
documents[name] = document;
}
const evidence = documents.TEST_EVIDENCE;
demand(new Set(evidence.artifacts.map(item => item.id)).size === evidence.artifacts.length, 'Duplicate artifact IDs');
demand(new Set(evidence.reports.map(item => item.id)).size === evidence.reports.length, 'Duplicate report IDs');
for (const artifact of evidence.artifacts) verifyBytes(readPackage(artifact.path), artifact.sha256);
const artifact = name => {
const item = evidence.artifacts.find(entry => entry.id === name);
demand(item, 'Missing indexed artifact');
const bytes = readPackage(item.path);
return item.path.endsWith('.gz') ? zlib.gunzipSync(bytes, { maxOutputLength: 8 * 1024 * 1024 }) : bytes;
};
for (const report of evidence.reports) {
const expectedCommit = report.bindingScope === 'HANDOFF_VERIFIER' ? evidence.verifierCommit : evidence.testedCommit;
demand(report.sourceCommit === expectedCommit, 'Report source correspondence missing');
let nativeExit = JSON.parse(artifact(report.processArtifactId).toString('utf8'));
for (const raw of report.processPointer.slice(1).split('/')) {
const key = raw.replaceAll('~1', '/').replaceAll('~0', '~');
demand(nativeExit !== null && Object.hasOwn(nativeExit, key), 'Missing native process observation');
nativeExit = nativeExit[key];
}
demand(nativeExit === report.exitCode, 'Native process exit does not match the report');
if (!report.nativeFormat) continue;
const measured = measureNative(report.nativeFormat, artifact(report.artifactId).toString('utf8'));
for (const key of ['passed', 'failed', 'skipped', 'retries', 'unexecuted']) demand(measured[key] === report.counts[key], `Wrong ${report.id} ${key}`);
demand(measured.membershipSha256 === report.membershipSha256, 'Changed report membership');
if (report.status === 'PASS') assertPass(measured, report.exitCode);
}
const discovery = measureNative('playwright', artifact(evidence.ordinaryDiscoveryArtifactId).toString('utf8')).names;
const ordinary = evidence.ordinaryReportIds.flatMap(id => {
const report = evidence.reports.find(item => item.id === id);
demand(report?.nativeFormat === 'playwright' && report.status === 'PASS', 'Missing ordinary browser lane');
return measureNative('playwright', artifact(report.artifactId).toString('utf8')).names;
}).sort();
demand(new Set(ordinary).size === ordinary.length && JSON.stringify(ordinary) === JSON.stringify(discovery), 'Browser discovery differs from executed unique membership');
const source = JSON.parse(artifact(evidence.sourceManifestArtifactId).toString('utf8'));
const after = JSON.parse(artifact(evidence.sourceAfterArtifactId).toString('utf8'));
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 tracked = execFileSync('rtk', ['proxy', 'git', 'ls-files', '-z'], { cwd: root, encoding: 'utf8' }).split('\0').filter(name => prefixes.some(prefix => name.startsWith(prefix))).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');
for (const [name, expected] of Object.entries(source)) {
demand(!path.isAbsolute(name) && !name.split(/[\\/]/).includes('..'), 'Unsafe source path');
const file = path.resolve(root, name);
demand(fs.realpathSync(file).startsWith(fs.realpathSync(root) + path.sep), 'Source escapes repository');
verifyBytes(fs.readFileSync(file), expected);
}
const verifierFiles = JSON.parse(artifact(evidence.verifierSourceArtifactId).toString('utf8'));
const expectedVerifierFiles = ['verify.mjs', 'verify.test.mjs', 'package.test.mjs', 'verify_git.py', 'HANDOFF_SCHEMA.json'].map(name => 'docs/automation/handoffs/pre-phase10/' + name).sort();
demand(JSON.stringify(Object.keys(verifierFiles).sort()) === JSON.stringify(expectedVerifierFiles), 'Missing verifier source membership');
for (const [name, expected] of Object.entries(verifierFiles)) verifyBytes(fs.readFileSync(path.join(root, name)), expected);
execFileSync('rtk', ['proxy', 'git', 'merge-base', '--is-ancestor', evidence.verifierCommit, 'HEAD'], { cwd: root });
execFileSync('rtk', ['proxy', 'git', 'merge-base', '--is-ancestor', evidence.testedCommit, 'HEAD'], { cwd: root });
const delta = execFileSync('rtk', ['proxy', 'git', 'diff', '--name-only', evidence.testedCommit, 'HEAD'], { cwd: root, encoding: 'utf8' }).trim().split(/\r?\n/).filter(Boolean);
demand(delta.every(name => name.startsWith('docs/automation/handoffs/pre-phase10/')), 'Application changed after tested baseline');
const declared = documents.ACCEPTANCE;
demand(declared.localCommit === evidence.testedCommit && declared.authority === 'EVIDENCE_ONLY' && declared.phase10DevelopmentReadiness === 'NO-GO' && declared.eligibleForAutomaticExecution === false, 'Unapproved entry claim');
const ids = new Set(documents.BLOCKERS.items.map(item => item.id));
demand(ids.size === documents.BLOCKERS.items.length, 'Duplicate blocker IDs');
demand(declared.unresolvedBlockerIds.every(id => ids.has(id)), 'Unresolved blocker is not registered');
const observedLocalCommit = execFileSync('rtk', ['proxy', 'git', 'rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim();
const observedWorkingTreeClean = !execFileSync('rtk', ['proxy', 'git', 'status', '--porcelain'], { cwd: root, encoding: 'utf8' }).trim();
const branch = execFileSync('rtk', ['proxy', 'git', 'branch', '--show-current'], { cwd: root, encoding: 'utf8' }).trim();
demand(branch === declared.sourceBranch, 'Wrong checkout branch');
return { integrity: 'PASS', testedCommit: evidence.testedCommit, observedLocalCommit, observedWorkingTreeClean, reports: evidence.reports.length, artifacts: evidence.artifacts.length, phase10: 'BLOCKED', ...requireProtectedEntry() };
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
try {
const result = validatePackage();
if (process.argv.includes('--verify-remote')) {
const branch = 'refs/heads/test/pre-phase10-ux-acceptance';
const line = execFileSync('rtk', ['proxy', 'git', 'ls-remote', '--exit-code', 'origin', branch], { cwd: root, encoding: 'utf8', timeout: 45000 }).trim();
demand(line === `${result.observedLocalCommit}\t${branch}`, 'Remote HEAD differs from current local HEAD');
result.currentRemotePublication = 'VERIFIED';
} else result.currentRemotePublication = 'NOT_RECHECKED';
console.log(JSON.stringify(result, null, 2));
if (process.argv.includes('--require-ready')) process.exitCode = 3;
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}
}
@@ -0,0 +1,35 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { measureNative, verifyBytes, assertPass, requireProtectedEntry } from './verify.mjs';
test('native Jest membership and counters agree before PASS', () => {
const result = measureNative('jest', JSON.stringify({ numTotalTests: 1, numPassedTests: 1, numFailedTests: 0, numPendingTests: 0, testResults: [{ name: 'fictional.test.ts', assertionResults: [{ fullName: 'preserves facts', status: 'passed' }] }] }));
assert.equal(result.passed, 1);
assertPass(result, 0);
});
test('zero, failing, skipped or retried results cannot qualify', () => {
for (const patch of [{ passed: 0 }, { failed: 1 }, { skipped: 1 }, { retries: 1 }])
assert.throws(() => assertPass({ passed: 1, failed: 0, skipped: 0, retries: 0, ...patch }, 0));
assert.throws(() => assertPass({ passed: 1, failed: 0, skipped: 0, retries: 0 }, 17));
});
test('tampered or missing evidence is rejected', () => {
assert.throws(() => verifyBytes(Buffer.from('changed'), '0'.repeat(64)));
assert.throws(() => verifyBytes(null, '0'.repeat(64)));
});
test('model-written GO and approval-shaped JSON cannot authorise Phase10', () => {
assert.equal(requireProtectedEntry({ status: 'GO', approved: true, ownerSignature: 'invented' }).eligible, false);
});
test('discovery counters cannot masquerade as an executed browser test', () => {
const report = { suites: [{ specs: [{ id: 'sample', tests: [{ projectName: 'chromium', results: [] }] }] }], stats: { expected: 1 } };
const result = measureNative('playwright', JSON.stringify(report));
assert.throws(() => assertPass(result, 0));
});
test('JUnit counters alone are not execution membership and entities are refused', () => {
assert.throws(() => assertPass(measureNative('junit', '<testsuite tests="83"/>'), 0));
assert.throws(() => measureNative('junit', '<!DOCTYPE x [<!ENTITY a "x">]><testsuite/>'));
const result = measureNative('junit', '<testsuite tests="1"><testcase classname="parser" name="grounded"/></testsuite>');
assert.equal(result.passed, 1);
});
test('oversized native reports are rejected before parsing', () => {
assert.throws(() => measureNative('jest', ' '.repeat(8 * 1024 * 1024 + 1)));
});
@@ -1,6 +1,7 @@
"""Bounded read-only Gitea diagnostics; credentials remain in Git's configured helper."""
import datetime
import json
import os
from pathlib import Path
import socket
import ssl
@@ -20,6 +21,8 @@ def main():
return result
url = git('remote', 'get-url', 'origin').stdout.strip()
assert url == 'https://git.cesnimda.uk/cesnimda/jobtrackingapp.git'
assert os.getenv('GIT_SSL_NO_VERIFY', '').lower() not in ('1', 'true', 'yes'), 'Git TLS verification is disabled in the environment'
assert git('config', '--get-urlmatch', 'http.sslVerify', url).stdout.strip().lower() not in ('false', '0', 'no'), 'Git TLS verification is disabled for this remote'
record = dict(observedUtc=datetime.datetime.now(datetime.timezone.utc).isoformat(), remote=url,
localCommit=git('rev-parse', 'HEAD').stdout.strip(), tlsVerificationDisabled=False)
try: