generated from coulomb/repo-seed
Implement source lock and submission package baseline
This commit is contained in:
@@ -42,6 +42,9 @@ The same CLI contracts are packaged by the container baseline. See
|
|||||||
[docs/CONTAINER.md](docs/CONTAINER.md). The dependency-light local API wraps
|
[docs/CONTAINER.md](docs/CONTAINER.md). The dependency-light local API wraps
|
||||||
those contracts for service and container operation; see
|
those contracts for service and container operation; see
|
||||||
[docs/LOCAL-SERVICE-API.md](docs/LOCAL-SERVICE-API.md).
|
[docs/LOCAL-SERVICE-API.md](docs/LOCAL-SERVICE-API.md).
|
||||||
|
New runs write a richer `sources.lock.json` and
|
||||||
|
`reports/submission-package.json` alongside the assessment package so reviewers
|
||||||
|
can inspect source, metadata, artifact, and boundary references.
|
||||||
|
|
||||||
The `sample-noop` extension exercises the guide-board contracts without invoking
|
The `sample-noop` extension exercises the guide-board contracts without invoking
|
||||||
an external harness. `sdk-fixture` demonstrates the extension SDK contracts for
|
an external harness. `sdk-fixture` demonstrates the extension SDK contracts for
|
||||||
|
|||||||
@@ -355,7 +355,9 @@ Stores run artifacts by reference and checksum:
|
|||||||
|
|
||||||
The first implementation builds the assessment package artifact manifest from
|
The first implementation builds the assessment package artifact manifest from
|
||||||
runner-emitted artifact refs and computes checksums for files inside the run
|
runner-emitted artifact refs and computes checksums for files inside the run
|
||||||
directory.
|
directory. New runs also write a source lock and a submission package manifest
|
||||||
|
that fingerprint reviewable run files and summarize runner or normalizer
|
||||||
|
metadata reported by extensions.
|
||||||
|
|
||||||
### Normalizer
|
### Normalizer
|
||||||
|
|
||||||
@@ -559,6 +561,18 @@ building complex runtime code.
|
|||||||
- `artifact_policy`
|
- `artifact_policy`
|
||||||
- `runtime_policy`
|
- `runtime_policy`
|
||||||
|
|
||||||
|
### `SourceLock`
|
||||||
|
|
||||||
|
- `framework_refs`
|
||||||
|
- `extension_refs`
|
||||||
|
- `frameworks`
|
||||||
|
- `extensions`
|
||||||
|
- `mapping_sets`
|
||||||
|
- `profiles`
|
||||||
|
- `policy_refs`
|
||||||
|
- `authorities`
|
||||||
|
- `metadata_hooks`
|
||||||
|
|
||||||
### `RawArtifact`
|
### `RawArtifact`
|
||||||
|
|
||||||
- `id`
|
- `id`
|
||||||
@@ -626,6 +640,19 @@ building complex runtime code.
|
|||||||
- `certification_boundary`
|
- `certification_boundary`
|
||||||
- `created_at`
|
- `created_at`
|
||||||
|
|
||||||
|
### `SubmissionPackage`
|
||||||
|
|
||||||
|
- `run_id`
|
||||||
|
- `package_identity`
|
||||||
|
- `source_lock_ref`
|
||||||
|
- `source_lock`
|
||||||
|
- `reports`
|
||||||
|
- `normalized_outputs`
|
||||||
|
- `profile_snapshots`
|
||||||
|
- `artifact_manifest`
|
||||||
|
- `reported_metadata`
|
||||||
|
- `certification_boundary`
|
||||||
|
|
||||||
## Result Vocabulary
|
## Result Vocabulary
|
||||||
|
|
||||||
The evidence model should allow these statuses:
|
The evidence model should allow these statuses:
|
||||||
@@ -714,6 +741,7 @@ runs/<run-id>/
|
|||||||
reports/
|
reports/
|
||||||
report.md
|
report.md
|
||||||
assessment-package.json
|
assessment-package.json
|
||||||
|
submission-package.json
|
||||||
exports/
|
exports/
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -787,7 +815,12 @@ Each run should lock:
|
|||||||
- test suite IDs,
|
- test suite IDs,
|
||||||
- mapping version,
|
- mapping version,
|
||||||
- target profile snapshot,
|
- target profile snapshot,
|
||||||
- waiver snapshot.
|
- expectation and waiver refs.
|
||||||
|
|
||||||
|
The current source lock remains backward-compatible with the original
|
||||||
|
`framework_refs` and `extension_refs` fields while adding checksummed profiles,
|
||||||
|
mapping-set refs, optional policy refs, authority descriptors, and metadata
|
||||||
|
hooks for runners and normalizers.
|
||||||
|
|
||||||
## Implementation Sequence
|
## Implementation Sequence
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ A completed CLI command prints a JSON result with:
|
|||||||
- `run_dir`: output directory,
|
- `run_dir`: output directory,
|
||||||
- `assessment_package`: JSON assessment package path,
|
- `assessment_package`: JSON assessment package path,
|
||||||
- `report`: Markdown report path,
|
- `report`: Markdown report path,
|
||||||
|
- `submission_package`: portable submission package manifest path,
|
||||||
- `retention_summary`: compact durable summary path.
|
- `retention_summary`: compact durable summary path.
|
||||||
|
|
||||||
The output directory uses this contract:
|
The output directory uses this contract:
|
||||||
@@ -84,15 +85,27 @@ The output directory uses this contract:
|
|||||||
```text
|
```text
|
||||||
run.json
|
run.json
|
||||||
plan.json
|
plan.json
|
||||||
|
sources.lock.json
|
||||||
|
target-profile.snapshot.json
|
||||||
|
assessment-profile.snapshot.json
|
||||||
retention-summary.json
|
retention-summary.json
|
||||||
normalized/evidence.json
|
normalized/evidence.json
|
||||||
normalized/findings.json
|
normalized/findings.json
|
||||||
normalized/mappings.json
|
normalized/mappings.json
|
||||||
reports/assessment-package.json
|
reports/assessment-package.json
|
||||||
reports/report.md
|
reports/report.md
|
||||||
|
reports/submission-package.json
|
||||||
artifacts/
|
artifacts/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`sources.lock.json` records the framework refs, extension versions, mapping
|
||||||
|
sets, profile snapshots, policy refs, authority refs, and extension metadata
|
||||||
|
hooks used for the run. `reports/submission-package.json` points at the
|
||||||
|
reviewable package files, includes checksums where files exist, carries the raw
|
||||||
|
artifact manifest, and repeats the certification boundary. It is a portable
|
||||||
|
handoff manifest for preparation evidence, not an authority-specific final
|
||||||
|
submission.
|
||||||
|
|
||||||
Use the retained run helpers for history:
|
Use the retained run helpers for history:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -71,7 +71,12 @@ The key runtime fields are:
|
|||||||
- `extension_type`: one of the supported archetypes from the architecture
|
- `extension_type`: one of the supported archetypes from the architecture
|
||||||
blueprint.
|
blueprint.
|
||||||
- `supported_frameworks`: framework IDs this extension can contribute evidence
|
- `supported_frameworks`: framework IDs this extension can contribute evidence
|
||||||
for.
|
for. Descriptor objects with `id`, `version`, `source_url`, and
|
||||||
|
`authority_ref` may be used when source metadata is available.
|
||||||
|
- `authorities`: authority IDs or descriptor objects with optional source URL,
|
||||||
|
version, license, and access notes.
|
||||||
|
- `metadata`: optional extension-level metadata such as adapter version or
|
||||||
|
source URL. The core preserves it in source locks and evidence metadata.
|
||||||
- `check_groups`: named groups that assessment profiles can select.
|
- `check_groups`: named groups that assessment profiles can select.
|
||||||
- `preflight_runner`: optional runner ID used before selected check groups.
|
- `preflight_runner`: optional runner ID used before selected check groups.
|
||||||
- `runner_entrypoints`: concrete runner declarations.
|
- `runner_entrypoints`: concrete runner declarations.
|
||||||
@@ -141,6 +146,11 @@ Example:
|
|||||||
"module_path": "src/open_cmis_tck/preflight.py",
|
"module_path": "src/open_cmis_tck/preflight.py",
|
||||||
"callable": "run",
|
"callable": "run",
|
||||||
"command": null,
|
"command": null,
|
||||||
|
"metadata": {
|
||||||
|
"harness_id": "opencmis-tck",
|
||||||
|
"harness_version": "extension-detected-or-declared",
|
||||||
|
"source_url": "https://chemistry.apache.org/java/opencmis.html"
|
||||||
|
},
|
||||||
"description": "Checks whether the CMIS Browser Binding endpoint is reachable."
|
"description": "Checks whether the CMIS Browser Binding endpoint is reachable."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -272,11 +282,20 @@ Result fields:
|
|||||||
- `observations`: human-readable observations.
|
- `observations`: human-readable observations.
|
||||||
- `facts`: structured facts extracted by the runner.
|
- `facts`: structured facts extracted by the runner.
|
||||||
- `artifact_refs`: references to raw artifacts written by the runner.
|
- `artifact_refs`: references to raw artifacts written by the runner.
|
||||||
|
- `requirement_refs`: optional requirement refs discovered by the runner.
|
||||||
|
- `metadata`: optional generic metadata such as `harness_version`,
|
||||||
|
`test_suite_id`, `adapter_version`, `source_url`, or native result IDs.
|
||||||
|
|
||||||
Artifact refs must be paths relative to the run directory. After runner
|
Artifact refs must be paths relative to the run directory. After runner
|
||||||
execution, the core fingerprints existing artifact refs into the assessment
|
execution, the core fingerprints existing artifact refs into the assessment
|
||||||
package `artifact_manifest`.
|
package `artifact_manifest`.
|
||||||
|
|
||||||
|
Runner metadata is merged with manifest entrypoint metadata and preserved under
|
||||||
|
evidence `facts.source_metadata`. The same metadata is also summarized in the
|
||||||
|
submission package manifest, which lets reviewers distinguish the extension
|
||||||
|
version from the harness or native test-suite version without adding
|
||||||
|
domain-specific fields to the core.
|
||||||
|
|
||||||
If a Python runner raises an exception, the core converts that failure into
|
If a Python runner raises an exception, the core converts that failure into
|
||||||
`infrastructure_error` evidence so the assessment package remains complete.
|
`infrastructure_error` evidence so the assessment package remains complete.
|
||||||
|
|
||||||
@@ -298,6 +317,9 @@ extension can add a normalizer descriptor:
|
|||||||
"module_path": "normalizers/native_probe.py",
|
"module_path": "normalizers/native_probe.py",
|
||||||
"callable": "normalize",
|
"callable": "normalize",
|
||||||
"runner_ref": "native-probe",
|
"runner_ref": "native-probe",
|
||||||
|
"metadata": {
|
||||||
|
"adapter_version": "0.1.0"
|
||||||
|
},
|
||||||
"description": "Converts native runner output into guide-board evidence."
|
"description": "Converts native runner output into guide-board evidence."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -340,6 +362,7 @@ The core merges the normalizer output over the runner result:
|
|||||||
- `observations` are appended.
|
- `observations` are appended.
|
||||||
- `facts` are merged.
|
- `facts` are merged.
|
||||||
- `artifact_refs` and `requirement_refs` are deduplicated.
|
- `artifact_refs` and `requirement_refs` are deduplicated.
|
||||||
|
- `metadata` is merged.
|
||||||
- `normalizer_refs` is recorded in evidence facts when any normalizer runs.
|
- `normalizer_refs` is recorded in evidence facts when any normalizer runs.
|
||||||
|
|
||||||
If a normalizer raises an exception, the step becomes
|
If a normalizer raises an exception, the step becomes
|
||||||
@@ -350,6 +373,25 @@ The bundled `extensions/sdk-fixture` extension is the copyable reference path
|
|||||||
for profile schemas, a native-output runner, a normalizer, mappings, and fixture
|
for profile schemas, a native-output runner, a normalizer, mappings, and fixture
|
||||||
profiles.
|
profiles.
|
||||||
|
|
||||||
|
## Source Lock And Submission Package
|
||||||
|
|
||||||
|
Every new run writes `sources.lock.json` and
|
||||||
|
`reports/submission-package.json`. Extension authors should treat source
|
||||||
|
metadata as part of the evidence contract:
|
||||||
|
|
||||||
|
- declare extension, authority, framework, runner, and normalizer metadata in
|
||||||
|
`extension.json` when it is static;
|
||||||
|
- return runner or normalizer `metadata` when versions, native result IDs, or
|
||||||
|
test-suite IDs are detected at runtime;
|
||||||
|
- keep mapping sets under `mappings/` so the core can checksum them in the
|
||||||
|
source lock;
|
||||||
|
- keep restricted or licensed assets referenced by metadata or artifacts rather
|
||||||
|
than vendored into the core.
|
||||||
|
|
||||||
|
The submission package manifest is generic guide-board output. Authority-specific
|
||||||
|
final submissions, trademark assertions, or certification conclusions remain
|
||||||
|
extension-owned or reviewer-owned.
|
||||||
|
|
||||||
## Result Statuses
|
## Result Statuses
|
||||||
|
|
||||||
Initial statuses:
|
Initial statuses:
|
||||||
|
|||||||
@@ -41,8 +41,38 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["candidate", "incubating", "active", "external", "deprecated"]
|
"enum": ["candidate", "incubating", "active", "external", "deprecated"]
|
||||||
},
|
},
|
||||||
"supported_frameworks": { "type": "array", "items": { "type": "string" } },
|
"supported_frameworks": {
|
||||||
"authorities": { "type": "array", "items": { "type": "string" } },
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": ["string", "object"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["id"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string" },
|
||||||
|
"version": { "type": ["string", "null"] },
|
||||||
|
"source_url": { "type": ["string", "null"] },
|
||||||
|
"authority_ref": { "type": ["string", "null"] },
|
||||||
|
"description": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"authorities": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": ["string", "object"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["id"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string" },
|
||||||
|
"name": { "type": ["string", "null"] },
|
||||||
|
"version": { "type": ["string", "null"] },
|
||||||
|
"source_url": { "type": ["string", "null"] },
|
||||||
|
"license": { "type": ["string", "null"] },
|
||||||
|
"access": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": { "type": "object" },
|
||||||
"profile_schemas": {
|
"profile_schemas": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -89,6 +119,7 @@
|
|||||||
"module_path": { "type": ["string", "null"] },
|
"module_path": { "type": ["string", "null"] },
|
||||||
"callable": { "type": ["string", "null"] },
|
"callable": { "type": ["string", "null"] },
|
||||||
"command": { "type": ["array", "null"], "items": { "type": "string" } },
|
"command": { "type": ["array", "null"], "items": { "type": "string" } },
|
||||||
|
"metadata": { "type": "object" },
|
||||||
"description": { "type": ["string", "null"] }
|
"description": { "type": ["string", "null"] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,6 +136,7 @@
|
|||||||
"module_path": { "type": "string" },
|
"module_path": { "type": "string" },
|
||||||
"callable": { "type": "string" },
|
"callable": { "type": "string" },
|
||||||
"runner_ref": { "type": ["string", "null"] },
|
"runner_ref": { "type": ["string", "null"] },
|
||||||
|
"metadata": { "type": "object" },
|
||||||
"description": { "type": ["string", "null"] }
|
"description": { "type": ["string", "null"] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
docs/schemas/source-lock.schema.json
Normal file
34
docs/schemas/source-lock.schema.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "Guide Board Source Lock",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"schema_version",
|
||||||
|
"created_at",
|
||||||
|
"framework_refs",
|
||||||
|
"extension_refs",
|
||||||
|
"frameworks",
|
||||||
|
"extensions",
|
||||||
|
"mapping_sets",
|
||||||
|
"profiles",
|
||||||
|
"policy_refs",
|
||||||
|
"authorities",
|
||||||
|
"metadata_hooks"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string" },
|
||||||
|
"schema_version": { "type": "string" },
|
||||||
|
"created_at": { "type": "string" },
|
||||||
|
"framework_refs": { "type": "array", "items": { "type": "string" } },
|
||||||
|
"extension_refs": { "type": "array", "items": { "type": "string" } },
|
||||||
|
"frameworks": { "type": "array", "items": { "type": "object" } },
|
||||||
|
"extensions": { "type": "array", "items": { "type": "object" } },
|
||||||
|
"mapping_sets": { "type": "array", "items": { "type": "object" } },
|
||||||
|
"profiles": { "type": "object" },
|
||||||
|
"policy_refs": { "type": "object" },
|
||||||
|
"authorities": { "type": "array", "items": { "type": "object" } },
|
||||||
|
"metadata_hooks": { "type": "object" }
|
||||||
|
}
|
||||||
|
}
|
||||||
36
docs/schemas/submission-package.schema.json
Normal file
36
docs/schemas/submission-package.schema.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"title": "Guide Board Submission Package Manifest",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"schema_version",
|
||||||
|
"run_id",
|
||||||
|
"created_at",
|
||||||
|
"package_identity",
|
||||||
|
"source_lock_ref",
|
||||||
|
"source_lock",
|
||||||
|
"reports",
|
||||||
|
"normalized_outputs",
|
||||||
|
"profile_snapshots",
|
||||||
|
"artifact_manifest",
|
||||||
|
"reported_metadata",
|
||||||
|
"certification_boundary"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string" },
|
||||||
|
"schema_version": { "type": "string" },
|
||||||
|
"run_id": { "type": "string" },
|
||||||
|
"created_at": { "type": "string" },
|
||||||
|
"package_identity": { "type": "object" },
|
||||||
|
"source_lock_ref": { "type": "string" },
|
||||||
|
"source_lock": { "type": "object" },
|
||||||
|
"reports": { "type": "array", "items": { "type": "object" } },
|
||||||
|
"normalized_outputs": { "type": "array", "items": { "type": "object" } },
|
||||||
|
"profile_snapshots": { "type": "array", "items": { "type": "object" } },
|
||||||
|
"artifact_manifest": { "type": "array", "items": { "type": "object" } },
|
||||||
|
"reported_metadata": { "type": "array", "items": { "type": "object" } },
|
||||||
|
"certification_boundary": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,10 @@
|
|||||||
"guide-board.sdk-fixture.v1"
|
"guide-board.sdk-fixture.v1"
|
||||||
],
|
],
|
||||||
"authorities": [],
|
"authorities": [],
|
||||||
|
"metadata": {
|
||||||
|
"adapter_version": "0.1.0",
|
||||||
|
"source_url": "https://example.invalid/guide-board/sdk-fixture"
|
||||||
|
},
|
||||||
"profile_schemas": [
|
"profile_schemas": [
|
||||||
"target-profile",
|
"target-profile",
|
||||||
"assessment-profile",
|
"assessment-profile",
|
||||||
@@ -44,6 +48,12 @@
|
|||||||
"module_path": "runners/native_probe.py",
|
"module_path": "runners/native_probe.py",
|
||||||
"callable": "run",
|
"callable": "run",
|
||||||
"command": null,
|
"command": null,
|
||||||
|
"metadata": {
|
||||||
|
"harness_id": "sdk-fixture-native-probe",
|
||||||
|
"harness_version": "1.0.0",
|
||||||
|
"test_suite_id": "sdk-fixture-suite-v1",
|
||||||
|
"source_url": "https://example.invalid/guide-board/sdk-fixture/native-probe"
|
||||||
|
},
|
||||||
"description": "Writes a tiny native result artifact for the SDK fixture normalizer."
|
"description": "Writes a tiny native result artifact for the SDK fixture normalizer."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -54,6 +64,10 @@
|
|||||||
"module_path": "normalizers/native_probe.py",
|
"module_path": "normalizers/native_probe.py",
|
||||||
"callable": "normalize",
|
"callable": "normalize",
|
||||||
"runner_ref": "native-probe",
|
"runner_ref": "native-probe",
|
||||||
|
"metadata": {
|
||||||
|
"adapter_version": "0.1.0",
|
||||||
|
"source_url": "https://example.invalid/guide-board/sdk-fixture/native-normalizer"
|
||||||
|
},
|
||||||
"description": "Converts the SDK fixture native result artifact into guide-board evidence."
|
"description": "Converts the SDK fixture native result artifact into guide-board evidence."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -25,4 +25,8 @@ def normalize(context: dict) -> dict:
|
|||||||
artifact_ref
|
artifact_ref
|
||||||
],
|
],
|
||||||
"requirement_refs": native_result.get("requirement_refs", []),
|
"requirement_refs": native_result.get("requirement_refs", []),
|
||||||
|
"metadata": {
|
||||||
|
"normalizer_id": "native-probe-normalizer",
|
||||||
|
"native_result_id": "sdk-fixture-native-result"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,4 +33,8 @@ def run(context: dict) -> dict:
|
|||||||
"artifact_refs": [
|
"artifact_refs": [
|
||||||
artifact_ref
|
artifact_ref
|
||||||
],
|
],
|
||||||
|
"metadata": {
|
||||||
|
"native_result_id": "sdk-fixture-native-result",
|
||||||
|
"test_suite_id": "sdk-fixture-suite-v1"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,107 @@ def build_artifact_manifest(
|
|||||||
return artifacts
|
return artifacts
|
||||||
|
|
||||||
|
|
||||||
|
def build_submission_manifest(
|
||||||
|
run_dir: Path,
|
||||||
|
run_metadata: dict[str, Any],
|
||||||
|
plan: dict[str, Any],
|
||||||
|
evidence: list[dict[str, Any]],
|
||||||
|
assessment_package: dict[str, Any],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Build a portable manifest for the files that make up a review package."""
|
||||||
|
source_lock = plan["source_lock"]
|
||||||
|
manifest = {
|
||||||
|
"id": f"submission-package:{run_metadata['id']}",
|
||||||
|
"schema_version": "guide-board.submission-package.v1",
|
||||||
|
"run_id": run_metadata["id"],
|
||||||
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"package_identity": {
|
||||||
|
"target_profile_ref": run_metadata["target_profile_ref"],
|
||||||
|
"assessment_profile_ref": run_metadata["assessment_profile_ref"],
|
||||||
|
"framework_refs": source_lock.get("framework_refs", []),
|
||||||
|
"extension_refs": source_lock.get("extension_refs", []),
|
||||||
|
},
|
||||||
|
"source_lock_ref": "sources.lock.json",
|
||||||
|
"source_lock": {
|
||||||
|
"id": source_lock.get("id"),
|
||||||
|
"schema_version": source_lock.get("schema_version"),
|
||||||
|
"checksum": _file_entry(run_dir, "sources.lock.json").get("checksum"),
|
||||||
|
"framework_refs": source_lock.get("framework_refs", []),
|
||||||
|
"extension_refs": source_lock.get("extension_refs", []),
|
||||||
|
},
|
||||||
|
"reports": _existing_file_entries(
|
||||||
|
run_dir,
|
||||||
|
[
|
||||||
|
("assessment-package", "reports/assessment-package.json"),
|
||||||
|
("markdown-report", "reports/report.md"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"normalized_outputs": _existing_file_entries(
|
||||||
|
run_dir,
|
||||||
|
[
|
||||||
|
("evidence", "normalized/evidence.json"),
|
||||||
|
("findings", "normalized/findings.json"),
|
||||||
|
("mappings", "normalized/mappings.json"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"profile_snapshots": _existing_file_entries(
|
||||||
|
run_dir,
|
||||||
|
[
|
||||||
|
("target-profile", "target-profile.snapshot.json"),
|
||||||
|
("assessment-profile", "assessment-profile.snapshot.json"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"artifact_manifest": assessment_package.get("artifact_manifest", []),
|
||||||
|
"reported_metadata": _reported_metadata(evidence),
|
||||||
|
"certification_boundary": assessment_package["certification_boundary"],
|
||||||
|
}
|
||||||
|
assert_valid(manifest, "submission-package")
|
||||||
|
return manifest
|
||||||
|
|
||||||
|
|
||||||
|
def _existing_file_entries(run_dir: Path, refs: list[tuple[str, str]]) -> list[dict[str, Any]]:
|
||||||
|
entries = []
|
||||||
|
for entry_id, ref in refs:
|
||||||
|
entry = _file_entry(run_dir, ref)
|
||||||
|
if entry:
|
||||||
|
entry["id"] = entry_id
|
||||||
|
entries.append(entry)
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def _file_entry(run_dir: Path, ref: str) -> dict[str, Any]:
|
||||||
|
path = (run_dir / ref).resolve()
|
||||||
|
try:
|
||||||
|
path.relative_to(run_dir.resolve())
|
||||||
|
except ValueError:
|
||||||
|
return {}
|
||||||
|
if not path.is_file():
|
||||||
|
return {}
|
||||||
|
return {
|
||||||
|
"path": ref,
|
||||||
|
"media_type": _media_type(path),
|
||||||
|
"checksum": f"sha256:{_sha256(path)}",
|
||||||
|
"size_bytes": path.stat().st_size,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _reported_metadata(evidence: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
records = []
|
||||||
|
for item in evidence:
|
||||||
|
metadata = item.get("facts", {}).get("source_metadata")
|
||||||
|
if not isinstance(metadata, dict) or not metadata:
|
||||||
|
continue
|
||||||
|
records.append(
|
||||||
|
{
|
||||||
|
"evidence_ref": item["id"],
|
||||||
|
"check_id": item["check_id"],
|
||||||
|
"extension_id": item["extension_id"],
|
||||||
|
"metadata": metadata,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return records
|
||||||
|
|
||||||
|
|
||||||
def _sha256(path: Path) -> str:
|
def _sha256(path: Path) -> str:
|
||||||
digest = hashlib.sha256()
|
digest = hashlib.sha256()
|
||||||
with path.open("rb") as handle:
|
with path.open("rb") as handle:
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from datetime import datetime, timezone
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from guide_board.artifacts import build_artifact_manifest
|
from guide_board.artifacts import build_artifact_manifest, build_submission_manifest
|
||||||
from guide_board.io import write_json
|
from guide_board.io import load_json, write_json
|
||||||
from guide_board.mapping import build_mapping_records, summarize_mappings
|
from guide_board.mapping import build_mapping_records, summarize_mappings
|
||||||
from guide_board.normalizers import normalize_step_result
|
from guide_board.normalizers import normalize_step_result
|
||||||
from guide_board.planning import build_run_plan
|
from guide_board.planning import build_run_plan
|
||||||
@@ -83,6 +83,7 @@ def run_assessment(
|
|||||||
"run_dir": str(run_dir),
|
"run_dir": str(run_dir),
|
||||||
"assessment_package": str(run_dir / "reports" / "assessment-package.json"),
|
"assessment_package": str(run_dir / "reports" / "assessment-package.json"),
|
||||||
"report": str(run_dir / "reports" / "report.md"),
|
"report": str(run_dir / "reports" / "report.md"),
|
||||||
|
"submission_package": str(run_dir / "reports" / "submission-package.json"),
|
||||||
"retention_summary": str(run_dir / "retention-summary.json"),
|
"retention_summary": str(run_dir / "retention-summary.json"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +156,14 @@ def _evidence_for_step(
|
|||||||
runner_ref = step.get("runner_ref")
|
runner_ref = step.get("runner_ref")
|
||||||
runner_result = run_step(root, run_dir, run_id, plan, step)
|
runner_result = run_step(root, run_dir, run_id, plan, step)
|
||||||
runner_result = normalize_step_result(root, run_dir, run_id, plan, step, runner_result)
|
runner_result = normalize_step_result(root, run_dir, run_id, plan, step, runner_result)
|
||||||
|
facts = {
|
||||||
|
"step_kind": step["kind"],
|
||||||
|
"runner_ref": runner_ref,
|
||||||
|
**runner_result["facts"],
|
||||||
|
}
|
||||||
|
source_metadata = _source_metadata_for_step(root, plan, step, runner_result)
|
||||||
|
if source_metadata:
|
||||||
|
facts["source_metadata"] = source_metadata
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": f"evidence:{step['id']}",
|
"id": f"evidence:{step['id']}",
|
||||||
@@ -164,11 +173,7 @@ def _evidence_for_step(
|
|||||||
"subject_ref": plan["target_profile_snapshot"]["id"],
|
"subject_ref": plan["target_profile_snapshot"]["id"],
|
||||||
"result": runner_result["result"],
|
"result": runner_result["result"],
|
||||||
"observations": runner_result["observations"],
|
"observations": runner_result["observations"],
|
||||||
"facts": {
|
"facts": facts,
|
||||||
"step_kind": step["kind"],
|
|
||||||
"runner_ref": runner_ref,
|
|
||||||
**runner_result["facts"],
|
|
||||||
},
|
|
||||||
"requirement_refs": _requirement_refs(plan, step, runner_result),
|
"requirement_refs": _requirement_refs(plan, step, runner_result),
|
||||||
"artifact_refs": runner_result["artifact_refs"],
|
"artifact_refs": runner_result["artifact_refs"],
|
||||||
"started_at": now,
|
"started_at": now,
|
||||||
@@ -198,6 +203,95 @@ def _runner_requirement_refs(runner_result: dict[str, Any] | None) -> list[str]:
|
|||||||
return [ref for ref in refs if isinstance(ref, str)]
|
return [ref for ref in refs if isinstance(ref, str)]
|
||||||
|
|
||||||
|
|
||||||
|
def _source_metadata_for_step(
|
||||||
|
root: Path,
|
||||||
|
plan: dict[str, Any],
|
||||||
|
step: dict[str, Any],
|
||||||
|
runner_result: dict[str, Any],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
runner_ref = step.get("runner_ref")
|
||||||
|
extension = _extension_snapshot(plan, step["extension_id"])
|
||||||
|
extension_path = _snapshot_path(root, extension)
|
||||||
|
manifest = load_json(extension_path / "extension.json")
|
||||||
|
metadata: dict[str, Any] = {
|
||||||
|
"extension": {
|
||||||
|
"id": step["extension_id"],
|
||||||
|
"version": extension.get("version"),
|
||||||
|
"metadata": _object_or_empty(manifest.get("metadata")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if runner_ref:
|
||||||
|
entrypoint = _runner_entrypoint(manifest, runner_ref)
|
||||||
|
metadata["runner"] = {
|
||||||
|
"id": runner_ref,
|
||||||
|
"kind": entrypoint.get("kind"),
|
||||||
|
"metadata": _object_or_empty(entrypoint.get("metadata")),
|
||||||
|
}
|
||||||
|
|
||||||
|
applied_normalizers = runner_result.get("facts", {}).get("normalizer_refs", [])
|
||||||
|
normalizers = []
|
||||||
|
if isinstance(applied_normalizers, list):
|
||||||
|
normalizer_ids = {item for item in applied_normalizers if isinstance(item, str)}
|
||||||
|
for normalizer in manifest.get("normalizers", []):
|
||||||
|
if isinstance(normalizer, dict) and normalizer.get("id") in normalizer_ids:
|
||||||
|
normalizers.append(
|
||||||
|
{
|
||||||
|
"id": normalizer["id"],
|
||||||
|
"kind": normalizer.get("kind"),
|
||||||
|
"runner_ref": normalizer.get("runner_ref"),
|
||||||
|
"metadata": _object_or_empty(normalizer.get("metadata")),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if normalizers:
|
||||||
|
metadata["normalizers"] = normalizers
|
||||||
|
|
||||||
|
reported = _object_or_empty(runner_result.get("metadata"))
|
||||||
|
if reported:
|
||||||
|
metadata["reported"] = reported
|
||||||
|
|
||||||
|
return _drop_empty_metadata(metadata)
|
||||||
|
|
||||||
|
|
||||||
|
def _extension_snapshot(plan: dict[str, Any], extension_id: str) -> dict[str, Any]:
|
||||||
|
for extension in plan["extension_snapshots"]:
|
||||||
|
if extension["id"] == extension_id:
|
||||||
|
return extension
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _snapshot_path(root: Path, extension: dict[str, Any]) -> Path:
|
||||||
|
path = Path(extension["path"])
|
||||||
|
return path if path.is_absolute() else root / path
|
||||||
|
|
||||||
|
|
||||||
|
def _runner_entrypoint(manifest: dict[str, Any], runner_ref: str) -> dict[str, Any]:
|
||||||
|
for entrypoint in manifest.get("runner_entrypoints", []):
|
||||||
|
if entrypoint.get("id") == runner_ref:
|
||||||
|
return entrypoint
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _object_or_empty(value: Any) -> dict[str, Any]:
|
||||||
|
return value if isinstance(value, dict) else {}
|
||||||
|
|
||||||
|
|
||||||
|
def _drop_empty_metadata(value: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
compact = {}
|
||||||
|
for key, child in value.items():
|
||||||
|
if isinstance(child, dict):
|
||||||
|
child = _drop_empty_metadata(child)
|
||||||
|
if isinstance(child, list):
|
||||||
|
child = [
|
||||||
|
_drop_empty_metadata(item) if isinstance(item, dict) else item
|
||||||
|
for item in child
|
||||||
|
]
|
||||||
|
child = [item for item in child if item]
|
||||||
|
if child:
|
||||||
|
compact[key] = child
|
||||||
|
return compact
|
||||||
|
|
||||||
|
|
||||||
def _dedupe(values: list[str]) -> list[str]:
|
def _dedupe(values: list[str]) -> list[str]:
|
||||||
seen = set()
|
seen = set()
|
||||||
deduped = []
|
deduped = []
|
||||||
@@ -340,6 +434,14 @@ def _write_run_directory(
|
|||||||
_markdown_report(run_metadata, assessment_package),
|
_markdown_report(run_metadata, assessment_package),
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
submission_manifest = build_submission_manifest(
|
||||||
|
run_dir,
|
||||||
|
run_metadata,
|
||||||
|
plan,
|
||||||
|
evidence,
|
||||||
|
assessment_package,
|
||||||
|
)
|
||||||
|
write_json(run_dir / "reports" / "submission-package.json", submission_manifest)
|
||||||
|
|
||||||
|
|
||||||
def _markdown_report(run_metadata: dict[str, Any], package: dict[str, Any]) -> str:
|
def _markdown_report(run_metadata: dict[str, Any], package: dict[str, Any]) -> str:
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ def _run_normalizer(
|
|||||||
},
|
},
|
||||||
"artifact_refs": runner_result.get("artifact_refs", []),
|
"artifact_refs": runner_result.get("artifact_refs", []),
|
||||||
"requirement_refs": runner_result.get("requirement_refs", []),
|
"requirement_refs": runner_result.get("requirement_refs", []),
|
||||||
|
"metadata": runner_result.get("metadata", {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
if not isinstance(result, dict):
|
if not isinstance(result, dict):
|
||||||
@@ -160,6 +161,12 @@ def _merge_result(
|
|||||||
_string_list(base.get("requirement_refs", []))
|
_string_list(base.get("requirement_refs", []))
|
||||||
+ _string_list(update.get("requirement_refs", []))
|
+ _string_list(update.get("requirement_refs", []))
|
||||||
)
|
)
|
||||||
|
if "metadata" in update:
|
||||||
|
metadata = dict(base.get("metadata", {}))
|
||||||
|
update_metadata = update.get("metadata", {})
|
||||||
|
if isinstance(update_metadata, dict):
|
||||||
|
metadata.update(update_metadata)
|
||||||
|
merged["metadata"] = metadata
|
||||||
return _coerce_result(merged)
|
return _coerce_result(merged)
|
||||||
|
|
||||||
|
|
||||||
@@ -173,6 +180,7 @@ def _coerce_result(value: dict[str, Any]) -> dict[str, Any]:
|
|||||||
"facts": facts,
|
"facts": facts,
|
||||||
"artifact_refs": _string_list(value.get("artifact_refs", [])),
|
"artifact_refs": _string_list(value.get("artifact_refs", [])),
|
||||||
"requirement_refs": _string_list(value.get("requirement_refs", [])),
|
"requirement_refs": _string_list(value.get("requirement_refs", [])),
|
||||||
|
"metadata": _object_or_empty(value.get("metadata")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -189,6 +197,10 @@ def _string_list(value: Any) -> list[str]:
|
|||||||
return [item for item in value if isinstance(item, str)]
|
return [item for item in value if isinstance(item, str)]
|
||||||
|
|
||||||
|
|
||||||
|
def _object_or_empty(value: Any) -> dict[str, Any]:
|
||||||
|
return value if isinstance(value, dict) else {}
|
||||||
|
|
||||||
|
|
||||||
def _dedupe(values: list[str]) -> list[str]:
|
def _dedupe(values: list[str]) -> list[str]:
|
||||||
seen = set()
|
seen = set()
|
||||||
deduped = []
|
deduped = []
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -96,6 +97,16 @@ def build_run_plan(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
source_lock = _build_source_lock(
|
||||||
|
root,
|
||||||
|
target_path,
|
||||||
|
assessment_path,
|
||||||
|
target,
|
||||||
|
assessment,
|
||||||
|
[extensions[extension_id] for extension_id in selected_extensions],
|
||||||
|
)
|
||||||
|
assert_valid(source_lock, "source-lock")
|
||||||
|
|
||||||
plan = {
|
plan = {
|
||||||
"id": f"plan-{_timestamp()}",
|
"id": f"plan-{_timestamp()}",
|
||||||
"assessment_profile_snapshot": assessment,
|
"assessment_profile_snapshot": assessment,
|
||||||
@@ -109,10 +120,7 @@ def build_run_plan(
|
|||||||
}
|
}
|
||||||
for extension_id in selected_extensions
|
for extension_id in selected_extensions
|
||||||
],
|
],
|
||||||
"source_lock": {
|
"source_lock": source_lock,
|
||||||
"framework_refs": assessment["framework_refs"],
|
|
||||||
"extension_refs": selected_extensions,
|
|
||||||
},
|
|
||||||
"profile_paths": {
|
"profile_paths": {
|
||||||
"target_profile_path": str(target_path.resolve()),
|
"target_profile_path": str(target_path.resolve()),
|
||||||
"assessment_profile_path": str(assessment_path.resolve()),
|
"assessment_profile_path": str(assessment_path.resolve()),
|
||||||
@@ -208,6 +216,270 @@ def _load_extension_profile_schema(
|
|||||||
return load_json(schema_path)
|
return load_json(schema_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_source_lock(
|
||||||
|
root: Path,
|
||||||
|
target_path: Path,
|
||||||
|
assessment_path: Path,
|
||||||
|
target: dict[str, Any],
|
||||||
|
assessment: dict[str, Any],
|
||||||
|
extensions: list[Extension],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
framework_refs = assessment["framework_refs"]
|
||||||
|
extension_refs = [extension.id for extension in extensions]
|
||||||
|
return {
|
||||||
|
"id": f"source-lock:{assessment['id']}:{target['id']}",
|
||||||
|
"schema_version": "guide-board.source-lock.v1",
|
||||||
|
"created_at": _now(),
|
||||||
|
"framework_refs": framework_refs,
|
||||||
|
"extension_refs": extension_refs,
|
||||||
|
"frameworks": _framework_records(framework_refs, extensions),
|
||||||
|
"extensions": [_extension_source_record(root, extension) for extension in extensions],
|
||||||
|
"mapping_sets": _mapping_source_records(root, extensions),
|
||||||
|
"profiles": {
|
||||||
|
"target": _file_source_record(
|
||||||
|
"target-profile",
|
||||||
|
target["id"],
|
||||||
|
target_path,
|
||||||
|
"target-profile.snapshot.json",
|
||||||
|
),
|
||||||
|
"assessment": _file_source_record(
|
||||||
|
"assessment-profile",
|
||||||
|
assessment["id"],
|
||||||
|
assessment_path,
|
||||||
|
"assessment-profile.snapshot.json",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"policy_refs": {
|
||||||
|
"expectations": _optional_policy_source_record(
|
||||||
|
root,
|
||||||
|
assessment_path,
|
||||||
|
assessment.get("expectations_ref"),
|
||||||
|
"expectation-set",
|
||||||
|
),
|
||||||
|
"waivers": _optional_policy_source_record(
|
||||||
|
root,
|
||||||
|
assessment_path,
|
||||||
|
assessment.get("waivers_ref"),
|
||||||
|
"waiver-set",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"authorities": _authority_source_records(extensions),
|
||||||
|
"metadata_hooks": {
|
||||||
|
"runner_entrypoints": _entrypoint_metadata_records(extensions),
|
||||||
|
"normalizers": _normalizer_metadata_records(extensions),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _framework_records(
|
||||||
|
framework_refs: list[str],
|
||||||
|
extensions: list[Extension],
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
records = []
|
||||||
|
for framework_ref in framework_refs:
|
||||||
|
declaring_extensions = [
|
||||||
|
extension.id
|
||||||
|
for extension in extensions
|
||||||
|
if framework_ref in _manifest_framework_ids(extension.manifest)
|
||||||
|
]
|
||||||
|
records.append(
|
||||||
|
{
|
||||||
|
"id": framework_ref,
|
||||||
|
"version": _version_hint(framework_ref),
|
||||||
|
"declared_by_extensions": declaring_extensions,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return records
|
||||||
|
|
||||||
|
|
||||||
|
def _extension_source_record(root: Path, extension: Extension) -> dict[str, Any]:
|
||||||
|
manifest_path = extension.path / "extension.json"
|
||||||
|
return {
|
||||||
|
"id": extension.id,
|
||||||
|
"version": extension.manifest["version"],
|
||||||
|
"path": _extension_path_ref(root, extension.path),
|
||||||
|
"source": extension.source,
|
||||||
|
"manifest_path": _display_path(root, manifest_path),
|
||||||
|
"manifest_checksum": _checksum_if_file(manifest_path),
|
||||||
|
"supported_frameworks": _manifest_framework_ids(extension.manifest),
|
||||||
|
"authorities": _authority_ids(extension.manifest.get("authorities", [])),
|
||||||
|
"certification_boundary": extension.manifest["certification_boundary"],
|
||||||
|
"metadata": _object_or_empty(extension.manifest.get("metadata")),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _mapping_source_records(root: Path, extensions: list[Extension]) -> list[dict[str, Any]]:
|
||||||
|
records = []
|
||||||
|
for extension in extensions:
|
||||||
|
for mapping_id in extension.manifest.get("mappings", []):
|
||||||
|
if not isinstance(mapping_id, str):
|
||||||
|
continue
|
||||||
|
mapping_path = extension.path / "mappings" / f"{mapping_id}.json"
|
||||||
|
record = {
|
||||||
|
"id": mapping_id,
|
||||||
|
"extension_id": extension.id,
|
||||||
|
"path": _display_path(root, mapping_path),
|
||||||
|
"exists": mapping_path.is_file(),
|
||||||
|
"checksum": _checksum_if_file(mapping_path),
|
||||||
|
"framework_refs": [],
|
||||||
|
}
|
||||||
|
if mapping_path.is_file():
|
||||||
|
mapping_set = load_json(mapping_path)
|
||||||
|
record["framework_refs"] = _string_list(mapping_set.get("framework_refs", []))
|
||||||
|
records.append(record)
|
||||||
|
return records
|
||||||
|
|
||||||
|
|
||||||
|
def _file_source_record(
|
||||||
|
kind: str,
|
||||||
|
profile_id: str,
|
||||||
|
path: Path,
|
||||||
|
snapshot_ref: str,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"kind": kind,
|
||||||
|
"id": profile_id,
|
||||||
|
"path": str(path.resolve()),
|
||||||
|
"checksum": _checksum_if_file(path),
|
||||||
|
"snapshot_ref": snapshot_ref,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _optional_policy_source_record(
|
||||||
|
root: Path,
|
||||||
|
assessment_path: Path,
|
||||||
|
ref: Any,
|
||||||
|
kind: str,
|
||||||
|
) -> dict[str, Any] | None:
|
||||||
|
if not isinstance(ref, str) or not ref:
|
||||||
|
return None
|
||||||
|
path = _resolve_assessment_ref(root, assessment_path, ref)
|
||||||
|
return {
|
||||||
|
"kind": kind,
|
||||||
|
"ref": ref,
|
||||||
|
"path": str(path.resolve()),
|
||||||
|
"exists": path.is_file(),
|
||||||
|
"checksum": _checksum_if_file(path),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _authority_source_records(extensions: list[Extension]) -> list[dict[str, Any]]:
|
||||||
|
records = []
|
||||||
|
for extension in extensions:
|
||||||
|
for authority in extension.manifest.get("authorities", []):
|
||||||
|
if isinstance(authority, str):
|
||||||
|
records.append({"id": authority, "extension_id": extension.id})
|
||||||
|
elif isinstance(authority, dict):
|
||||||
|
record = {
|
||||||
|
"id": authority.get("id"),
|
||||||
|
"extension_id": extension.id,
|
||||||
|
}
|
||||||
|
for key in ("name", "version", "source_url", "license", "access"):
|
||||||
|
if key in authority:
|
||||||
|
record[key] = authority[key]
|
||||||
|
records.append(record)
|
||||||
|
return [record for record in records if isinstance(record.get("id"), str)]
|
||||||
|
|
||||||
|
|
||||||
|
def _entrypoint_metadata_records(extensions: list[Extension]) -> list[dict[str, Any]]:
|
||||||
|
records = []
|
||||||
|
for extension in extensions:
|
||||||
|
for entrypoint in extension.manifest.get("runner_entrypoints", []):
|
||||||
|
if not isinstance(entrypoint, dict):
|
||||||
|
continue
|
||||||
|
records.append(
|
||||||
|
{
|
||||||
|
"extension_id": extension.id,
|
||||||
|
"id": entrypoint.get("id"),
|
||||||
|
"kind": entrypoint.get("kind"),
|
||||||
|
"metadata": _object_or_empty(entrypoint.get("metadata")),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return records
|
||||||
|
|
||||||
|
|
||||||
|
def _normalizer_metadata_records(extensions: list[Extension]) -> list[dict[str, Any]]:
|
||||||
|
records = []
|
||||||
|
for extension in extensions:
|
||||||
|
for normalizer in extension.manifest.get("normalizers", []):
|
||||||
|
if not isinstance(normalizer, dict):
|
||||||
|
continue
|
||||||
|
records.append(
|
||||||
|
{
|
||||||
|
"extension_id": extension.id,
|
||||||
|
"id": normalizer.get("id"),
|
||||||
|
"kind": normalizer.get("kind"),
|
||||||
|
"runner_ref": normalizer.get("runner_ref"),
|
||||||
|
"metadata": _object_or_empty(normalizer.get("metadata")),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return records
|
||||||
|
|
||||||
|
|
||||||
|
def _manifest_framework_ids(manifest: dict[str, Any]) -> list[str]:
|
||||||
|
values = []
|
||||||
|
for framework in manifest.get("supported_frameworks", []):
|
||||||
|
if isinstance(framework, str):
|
||||||
|
values.append(framework)
|
||||||
|
elif isinstance(framework, dict) and isinstance(framework.get("id"), str):
|
||||||
|
values.append(framework["id"])
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def _authority_ids(authorities: list[Any]) -> list[str]:
|
||||||
|
values = []
|
||||||
|
for authority in authorities:
|
||||||
|
if isinstance(authority, str):
|
||||||
|
values.append(authority)
|
||||||
|
elif isinstance(authority, dict) and isinstance(authority.get("id"), str):
|
||||||
|
values.append(authority["id"])
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_assessment_ref(root: Path, assessment_path: Path, ref: str) -> Path:
|
||||||
|
ref_path = Path(ref)
|
||||||
|
if ref_path.is_absolute():
|
||||||
|
return ref_path
|
||||||
|
root_relative = root / ref_path
|
||||||
|
if root_relative.exists():
|
||||||
|
return root_relative
|
||||||
|
return assessment_path.resolve().parent / ref_path
|
||||||
|
|
||||||
|
|
||||||
|
def _checksum_if_file(path: Path) -> str | None:
|
||||||
|
if not path.is_file():
|
||||||
|
return None
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
with path.open("rb") as handle:
|
||||||
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||||
|
digest.update(chunk)
|
||||||
|
return f"sha256:{digest.hexdigest()}"
|
||||||
|
|
||||||
|
|
||||||
|
def _version_hint(ref: str) -> str | None:
|
||||||
|
for part in reversed(ref.replace("-", ".").split(".")):
|
||||||
|
if len(part) > 1 and part[0].lower() == "v" and any(char.isdigit() for char in part[1:]):
|
||||||
|
return part
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _object_or_empty(value: Any) -> dict[str, Any]:
|
||||||
|
return value if isinstance(value, dict) else {}
|
||||||
|
|
||||||
|
|
||||||
|
def _string_list(value: Any) -> list[str]:
|
||||||
|
if not isinstance(value, list):
|
||||||
|
return []
|
||||||
|
return [item for item in value if isinstance(item, str)]
|
||||||
|
|
||||||
|
|
||||||
|
def _display_path(root: Path, path: Path) -> str:
|
||||||
|
try:
|
||||||
|
return str(path.resolve().relative_to(root.resolve()))
|
||||||
|
except ValueError:
|
||||||
|
return str(path.resolve())
|
||||||
|
|
||||||
|
|
||||||
def _extension_path_ref(root: Path, path: Path) -> str:
|
def _extension_path_ref(root: Path, path: Path) -> str:
|
||||||
try:
|
try:
|
||||||
return str(path.resolve().relative_to(root.resolve()))
|
return str(path.resolve().relative_to(root.resolve()))
|
||||||
@@ -215,5 +487,9 @@ def _extension_path_ref(root: Path, path: Path) -> str:
|
|||||||
return str(path.resolve())
|
return str(path.resolve())
|
||||||
|
|
||||||
|
|
||||||
|
def _now() -> str:
|
||||||
|
return datetime.now(timezone.utc).isoformat()
|
||||||
|
|
||||||
|
|
||||||
def _timestamp() -> str:
|
def _timestamp() -> str:
|
||||||
return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ def build_retention_summary(
|
|||||||
"report_refs": [
|
"report_refs": [
|
||||||
"reports/assessment-package.json",
|
"reports/assessment-package.json",
|
||||||
"reports/report.md",
|
"reports/report.md",
|
||||||
|
"reports/submission-package.json",
|
||||||
],
|
],
|
||||||
"artifact_retention": {
|
"artifact_retention": {
|
||||||
"policy": plan["assessment_profile_snapshot"].get("retention_policy", {}),
|
"policy": plan["assessment_profile_snapshot"].get("retention_policy", {}),
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ def run_step(
|
|||||||
"runner_kind": "external",
|
"runner_kind": "external",
|
||||||
},
|
},
|
||||||
"artifact_refs": [],
|
"artifact_refs": [],
|
||||||
|
"requirement_refs": [],
|
||||||
|
"metadata": _object_or_empty(entrypoint.get("metadata")),
|
||||||
}
|
}
|
||||||
if entrypoint["kind"] == "command":
|
if entrypoint["kind"] == "command":
|
||||||
return _run_command(root, run_dir, run_id, plan, step, extension_path, entrypoint)
|
return _run_command(root, run_dir, run_id, plan, step, extension_path, entrypoint)
|
||||||
@@ -63,6 +65,8 @@ def _no_runner_result(step: dict[str, Any]) -> dict[str, Any]:
|
|||||||
"runner_kind": None,
|
"runner_kind": None,
|
||||||
},
|
},
|
||||||
"artifact_refs": [],
|
"artifact_refs": [],
|
||||||
|
"requirement_refs": [],
|
||||||
|
"metadata": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -118,6 +122,8 @@ def _run_python_module(
|
|||||||
"error_type": type(exc).__name__,
|
"error_type": type(exc).__name__,
|
||||||
},
|
},
|
||||||
"artifact_refs": [],
|
"artifact_refs": [],
|
||||||
|
"requirement_refs": [],
|
||||||
|
"metadata": _object_or_empty(entrypoint.get("metadata")),
|
||||||
}
|
}
|
||||||
if not isinstance(result, dict):
|
if not isinstance(result, dict):
|
||||||
raise ValidationError(f"{entrypoint['id']}: runner must return an object")
|
raise ValidationError(f"{entrypoint['id']}: runner must return an object")
|
||||||
@@ -126,6 +132,8 @@ def _run_python_module(
|
|||||||
"observations": result.get("observations", []),
|
"observations": result.get("observations", []),
|
||||||
"facts": result.get("facts", {}),
|
"facts": result.get("facts", {}),
|
||||||
"artifact_refs": result.get("artifact_refs", []),
|
"artifact_refs": result.get("artifact_refs", []),
|
||||||
|
"requirement_refs": result.get("requirement_refs", []),
|
||||||
|
"metadata": _merge_metadata(entrypoint.get("metadata"), result.get("metadata")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -192,6 +200,8 @@ def _run_command(
|
|||||||
"command": command,
|
"command": command,
|
||||||
},
|
},
|
||||||
"artifact_refs": [str(context_path.relative_to(run_dir))],
|
"artifact_refs": [str(context_path.relative_to(run_dir))],
|
||||||
|
"requirement_refs": [],
|
||||||
|
"metadata": _object_or_empty(entrypoint.get("metadata")),
|
||||||
}
|
}
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
return {
|
return {
|
||||||
@@ -206,6 +216,8 @@ def _run_command(
|
|||||||
"command": command,
|
"command": command,
|
||||||
},
|
},
|
||||||
"artifact_refs": [str(context_path.relative_to(run_dir))],
|
"artifact_refs": [str(context_path.relative_to(run_dir))],
|
||||||
|
"requirement_refs": [],
|
||||||
|
"metadata": _object_or_empty(entrypoint.get("metadata")),
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed = _parse_runner_stdout(completed.stdout)
|
parsed = _parse_runner_stdout(completed.stdout)
|
||||||
@@ -225,6 +237,8 @@ def _run_command(
|
|||||||
"command": command,
|
"command": command,
|
||||||
},
|
},
|
||||||
"artifact_refs": [str(context_path.relative_to(run_dir))],
|
"artifact_refs": [str(context_path.relative_to(run_dir))],
|
||||||
|
"requirement_refs": [],
|
||||||
|
"metadata": _object_or_empty(entrypoint.get("metadata")),
|
||||||
}
|
}
|
||||||
|
|
||||||
facts = parsed.get("facts", {})
|
facts = parsed.get("facts", {})
|
||||||
@@ -245,6 +259,9 @@ def _run_command(
|
|||||||
if not isinstance(artifact_refs, list):
|
if not isinstance(artifact_refs, list):
|
||||||
artifact_refs = []
|
artifact_refs = []
|
||||||
artifact_refs.append(str(context_path.relative_to(run_dir)))
|
artifact_refs.append(str(context_path.relative_to(run_dir)))
|
||||||
|
requirement_refs = parsed.get("requirement_refs", [])
|
||||||
|
if not isinstance(requirement_refs, list):
|
||||||
|
requirement_refs = []
|
||||||
|
|
||||||
result = parsed.get("result", "unknown")
|
result = parsed.get("result", "unknown")
|
||||||
if completed.returncode != 0 and result in {"pass", "warning", "manual", "skipped"}:
|
if completed.returncode != 0 and result in {"pass", "warning", "manual", "skipped"}:
|
||||||
@@ -258,6 +275,8 @@ def _run_command(
|
|||||||
"observations": observations,
|
"observations": observations,
|
||||||
"facts": facts,
|
"facts": facts,
|
||||||
"artifact_refs": artifact_refs,
|
"artifact_refs": artifact_refs,
|
||||||
|
"requirement_refs": requirement_refs,
|
||||||
|
"metadata": _merge_metadata(entrypoint.get("metadata"), parsed.get("metadata")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -328,5 +347,17 @@ def _parse_runner_stdout(stdout: str) -> dict[str, Any] | None:
|
|||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_metadata(*values: Any) -> dict[str, Any]:
|
||||||
|
merged: dict[str, Any] = {}
|
||||||
|
for value in values:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
merged.update(value)
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
||||||
|
def _object_or_empty(value: Any) -> dict[str, Any]:
|
||||||
|
return value if isinstance(value, dict) else {}
|
||||||
|
|
||||||
|
|
||||||
def _safe_id(value: str) -> str:
|
def _safe_id(value: str) -> str:
|
||||||
return "".join(char if char.isalnum() or char in {"-", "_"} else "_" for char in value)
|
return "".join(char if char.isalnum() or char in {"-", "_"} else "_" for char in value)
|
||||||
|
|||||||
@@ -75,6 +75,17 @@ class CoreArchitectureTests(unittest.TestCase):
|
|||||||
plan["ordered_steps"][1]["requirement_refs"],
|
plan["ordered_steps"][1]["requirement_refs"],
|
||||||
["guide-board.sample-readiness.v0.profile-shape"],
|
["guide-board.sample-readiness.v0.profile-shape"],
|
||||||
)
|
)
|
||||||
|
assert_valid(plan["source_lock"], "source-lock")
|
||||||
|
self.assertEqual(plan["source_lock"]["schema_version"], "guide-board.source-lock.v1")
|
||||||
|
self.assertEqual(plan["source_lock"]["framework_refs"], ["guide-board.sample-readiness.v0"])
|
||||||
|
self.assertEqual(plan["source_lock"]["extension_refs"], ["sample-noop"])
|
||||||
|
self.assertEqual(
|
||||||
|
plan["source_lock"]["profiles"]["target"]["snapshot_ref"],
|
||||||
|
"target-profile.snapshot.json",
|
||||||
|
)
|
||||||
|
self.assertTrue(plan["source_lock"]["profiles"]["target"]["checksum"].startswith("sha256:"))
|
||||||
|
self.assertEqual(plan["source_lock"]["mapping_sets"][0]["id"], "sample-readiness-map")
|
||||||
|
self.assertTrue(plan["source_lock"]["mapping_sets"][0]["checksum"].startswith("sha256:"))
|
||||||
|
|
||||||
def test_runs_external_extension_from_separate_repo(self) -> None:
|
def test_runs_external_extension_from_separate_repo(self) -> None:
|
||||||
with TemporaryDirectory() as temporary_directory:
|
with TemporaryDirectory() as temporary_directory:
|
||||||
@@ -237,8 +248,37 @@ class CoreArchitectureTests(unittest.TestCase):
|
|||||||
check_evidence["artifact_refs"],
|
check_evidence["artifact_refs"],
|
||||||
["artifacts/sdk-fixture/native-result.json"],
|
["artifacts/sdk-fixture/native-result.json"],
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
check_evidence["facts"]["source_metadata"]["runner"]["metadata"]["harness_version"],
|
||||||
|
"1.0.0",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
check_evidence["facts"]["source_metadata"]["reported"]["native_result_id"],
|
||||||
|
"sdk-fixture-native-result",
|
||||||
|
)
|
||||||
self.assertEqual(mappings[0]["target_id"], "normalizer-plugin")
|
self.assertEqual(mappings[0]["target_id"], "normalizer-plugin")
|
||||||
self.assertEqual(assessment_package["summary"], {"pass": 1, "skipped": 1})
|
self.assertEqual(assessment_package["summary"], {"pass": 1, "skipped": 1})
|
||||||
|
self.assertEqual(
|
||||||
|
assessment_package["source_lock"]["metadata_hooks"]["runner_entrypoints"][0][
|
||||||
|
"metadata"
|
||||||
|
]["harness_id"],
|
||||||
|
"sdk-fixture-native-probe",
|
||||||
|
)
|
||||||
|
submission_package = json.loads(
|
||||||
|
(run_dir / "reports" / "submission-package.json").read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
assert_valid(submission_package, "submission-package")
|
||||||
|
self.assertEqual(submission_package["source_lock"]["id"], "source-lock:sdk-fixture-assessment:sdk-fixture-target")
|
||||||
|
self.assertEqual(
|
||||||
|
submission_package["reported_metadata"][1]["metadata"]["reported"][
|
||||||
|
"native_result_id"
|
||||||
|
],
|
||||||
|
"sdk-fixture-native-result",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
submission_package["artifact_manifest"][0]["checksum"],
|
||||||
|
assessment_package["artifact_manifest"][0]["checksum"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_runs_sample_noop_assessment(self) -> None:
|
def test_runs_sample_noop_assessment(self) -> None:
|
||||||
with TemporaryDirectory() as temporary_directory:
|
with TemporaryDirectory() as temporary_directory:
|
||||||
@@ -256,6 +296,7 @@ class CoreArchitectureTests(unittest.TestCase):
|
|||||||
self.assertTrue((run_dir / "normalized" / "evidence.json").exists())
|
self.assertTrue((run_dir / "normalized" / "evidence.json").exists())
|
||||||
self.assertTrue((run_dir / "reports" / "assessment-package.json").exists())
|
self.assertTrue((run_dir / "reports" / "assessment-package.json").exists())
|
||||||
self.assertTrue((run_dir / "reports" / "report.md").exists())
|
self.assertTrue((run_dir / "reports" / "report.md").exists())
|
||||||
|
self.assertTrue((run_dir / "reports" / "submission-package.json").exists())
|
||||||
retention = json.loads(
|
retention = json.loads(
|
||||||
(run_dir / "retention-summary.json").read_text(encoding="utf-8")
|
(run_dir / "retention-summary.json").read_text(encoding="utf-8")
|
||||||
)
|
)
|
||||||
@@ -263,12 +304,26 @@ class CoreArchitectureTests(unittest.TestCase):
|
|||||||
result["retention_summary"],
|
result["retention_summary"],
|
||||||
str(run_dir / "retention-summary.json"),
|
str(run_dir / "retention-summary.json"),
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
result["submission_package"],
|
||||||
|
str(run_dir / "reports" / "submission-package.json"),
|
||||||
|
)
|
||||||
self.assertEqual(retention["summary"]["status"], "completed")
|
self.assertEqual(retention["summary"]["status"], "completed")
|
||||||
self.assertEqual(retention["summary"]["artifact_count"], 0)
|
self.assertEqual(retention["summary"]["artifact_count"], 0)
|
||||||
|
self.assertIn("reports/submission-package.json", retention["report_refs"])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
retention["artifact_retention"]["policy"],
|
retention["artifact_retention"]["policy"],
|
||||||
{"raw_artifact_days": 0, "summary_days": 365},
|
{"raw_artifact_days": 0, "summary_days": 365},
|
||||||
)
|
)
|
||||||
|
submission = json.loads(
|
||||||
|
(run_dir / "reports" / "submission-package.json").read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
assert_valid(submission, "submission-package")
|
||||||
|
self.assertEqual(submission["package_identity"]["target_profile_ref"], "sample-repository")
|
||||||
|
self.assertEqual(
|
||||||
|
[entry["path"] for entry in submission["reports"]],
|
||||||
|
["reports/assessment-package.json", "reports/report.md"],
|
||||||
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[run["run_id"] for run in list_retained_runs(Path(temporary_directory))],
|
[run["run_id"] for run in list_retained_runs(Path(temporary_directory))],
|
||||||
[result["run_id"]],
|
[result["run_id"]],
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ type: workplan
|
|||||||
title: "Source Lock And Submission Package Baseline"
|
title: "Source Lock And Submission Package Baseline"
|
||||||
repo: guide-board
|
repo: guide-board
|
||||||
domain: markitect
|
domain: markitect
|
||||||
status: active
|
status: completed
|
||||||
owner: codex
|
owner: codex
|
||||||
planning_priority: high
|
planning_priority: high
|
||||||
planning_order: 4
|
planning_order: 4
|
||||||
created: "2026-05-15"
|
created: "2026-05-15"
|
||||||
updated: "2026-05-15"
|
updated: "2026-05-16"
|
||||||
state_hub_workstream_id: "6dd2832b-d1d9-43bc-ad5c-d16f399930dc"
|
state_hub_workstream_id: "6dd2832b-d1d9-43bc-ad5c-d16f399930dc"
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ submission rules, and licensed or restricted assets remain extension-owned.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: GUIDE-BOARD-WP-0004-T001
|
id: GUIDE-BOARD-WP-0004-T001
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "d5a7a18f-941b-47b8-9992-2cb54bc5ad06"
|
state_hub_task_id: "d5a7a18f-941b-47b8-9992-2cb54bc5ad06"
|
||||||
```
|
```
|
||||||
@@ -55,11 +55,20 @@ Acceptance:
|
|||||||
- Keep the schema backward-compatible with existing retained runs.
|
- Keep the schema backward-compatible with existing retained runs.
|
||||||
- Add tests for source lock shape and retained run compatibility.
|
- Add tests for source lock shape and retained run compatibility.
|
||||||
|
|
||||||
|
Progress:
|
||||||
|
|
||||||
|
- Added `docs/schemas/source-lock.schema.json`.
|
||||||
|
- Expanded run-plan source locks with framework, extension, mapping-set,
|
||||||
|
profile snapshot, policy-ref, authority, and metadata-hook records.
|
||||||
|
- Preserved the original `framework_refs` and `extension_refs` fields for
|
||||||
|
retained-run compatibility.
|
||||||
|
- Added tests for source-lock shape and older retained summary compatibility.
|
||||||
|
|
||||||
## D4.2 - Harness And Extension Metadata Hooks
|
## D4.2 - Harness And Extension Metadata Hooks
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: GUIDE-BOARD-WP-0004-T002
|
id: GUIDE-BOARD-WP-0004-T002
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "7abd5a66-5784-41b9-a361-6572290923cc"
|
state_hub_task_id: "7abd5a66-5784-41b9-a361-6572290923cc"
|
||||||
```
|
```
|
||||||
@@ -75,11 +84,20 @@ Acceptance:
|
|||||||
provide this metadata yet.
|
provide this metadata yet.
|
||||||
- Cover the SDK fixture and at least one no-metadata extension path in tests.
|
- Cover the SDK fixture and at least one no-metadata extension path in tests.
|
||||||
|
|
||||||
|
Progress:
|
||||||
|
|
||||||
|
- Added optional manifest metadata for extensions, authorities, frameworks,
|
||||||
|
runner entrypoints, and normalizers.
|
||||||
|
- Preserved runner and normalizer returned `metadata` and requirement refs.
|
||||||
|
- Recorded merged metadata under evidence `facts.source_metadata`.
|
||||||
|
- Updated `sdk-fixture` to exercise harness, test-suite, adapter, source URL,
|
||||||
|
and native result metadata while keeping `sample-noop` as a no-metadata path.
|
||||||
|
|
||||||
## D4.3 - Submission Package Manifest
|
## D4.3 - Submission Package Manifest
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: GUIDE-BOARD-WP-0004-T003
|
id: GUIDE-BOARD-WP-0004-T003
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "c54273d6-1fc2-4444-92cf-74f2a5e614ec"
|
state_hub_task_id: "c54273d6-1fc2-4444-92cf-74f2a5e614ec"
|
||||||
```
|
```
|
||||||
@@ -95,11 +113,21 @@ Acceptance:
|
|||||||
packs.
|
packs.
|
||||||
- Document how this differs from an authority-specific final submission.
|
- Document how this differs from an authority-specific final submission.
|
||||||
|
|
||||||
|
Progress:
|
||||||
|
|
||||||
|
- Added `docs/schemas/submission-package.schema.json`.
|
||||||
|
- Wrote `reports/submission-package.json` for each run.
|
||||||
|
- Included package identity, source lock checksum, report paths, normalized
|
||||||
|
output paths, profile snapshots, artifact manifest entries, reported
|
||||||
|
metadata, and the certification boundary.
|
||||||
|
- Exposed the submission manifest path in CLI/service run results and retained
|
||||||
|
report refs.
|
||||||
|
|
||||||
## D4.4 - Documentation And Acceptance Tests
|
## D4.4 - Documentation And Acceptance Tests
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: GUIDE-BOARD-WP-0004-T004
|
id: GUIDE-BOARD-WP-0004-T004
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "ad37baeb-973c-4399-96d0-c9cb7fc6b761"
|
state_hub_task_id: "ad37baeb-973c-4399-96d0-c9cb7fc6b761"
|
||||||
```
|
```
|
||||||
@@ -113,6 +141,15 @@ Acceptance:
|
|||||||
- Include compatibility notes for older retained runs.
|
- Include compatibility notes for older retained runs.
|
||||||
- Keep the output paths aligned with existing CLI and service result retrieval.
|
- Keep the output paths aligned with existing CLI and service result retrieval.
|
||||||
|
|
||||||
|
Progress:
|
||||||
|
|
||||||
|
- Updated assessment operations, extension SDK, architecture blueprint, and
|
||||||
|
README references.
|
||||||
|
- Added focused unit assertions for the sample and SDK fixture assessment
|
||||||
|
outputs.
|
||||||
|
- Kept retained run listing compatible with older `retention-summary.json`
|
||||||
|
files that do not reference a submission manifest.
|
||||||
|
|
||||||
## Definition Of Done
|
## Definition Of Done
|
||||||
|
|
||||||
- Every new run writes a richer source lock and submission package manifest.
|
- Every new run writes a richer source lock and submission package manifest.
|
||||||
|
|||||||
Reference in New Issue
Block a user