# Guide Board Extension SDK Status: draft Created: 2026-05-07 ## Purpose This document defines the first extension integration contract for `guide-board`. It is intentionally small: extensions declare metadata in `extension.json`, the core discovers them, and runners can produce normalized evidence through a stable dictionary contract. ## Extension Layout Bundled incubating extensions live under: ```text extensions// INTENT.md extension.json src/ docs/ schemas/ evidence-requests/ checks/ mappings/ profiles/ runners/ normalizers/ reports/ workplans/ ``` Production extensions may also live in their own repositories. The repository root is then the extension root and must contain `extension.json`: ```text open-cmis-tck/ INTENT.md extension.json src/ mappings/ profiles/ runners/ workplans/ ``` Pass external extension repos to the core CLI with: ```sh guide-board --extension-dir ../open-cmis-tck extensions list ``` Multiple `--extension-dir` values are allowed. `GUIDE_BOARD_EXTENSION_PATHS` may also provide an OS-path-separated list for local automation and containers. Only `INTENT.md` and `extension.json` are required for discovery. Additional folders appear as the extension grows. ## Manifest Contract `extension.json` must validate against: ```text docs/schemas/extension-manifest.schema.json ``` The key runtime fields are: - `id`: must match the extension directory name. - `extension_type`: one of the supported archetypes from the architecture blueprint. - `supported_frameworks`: framework IDs this extension can contribute evidence for. - `check_groups`: named groups that assessment profiles can select. - `preflight_runner`: optional runner ID used before selected check groups. - `runner_entrypoints`: concrete runner declarations. - `normalizers`: optional plug-ins that convert native runner output into the stable runner-result shape before evidence is written. - `mappings`: mapping set IDs under `mappings/.json`. - `certification_boundary`: explicit statement of what the extension does not certify. `profile_schemas` may use the original string shorthand for core schemas: ```json ["target-profile", "assessment-profile"] ``` Extensions that need stricter domain-specific validation can add schema descriptors: ```json [ "target-profile", "assessment-profile", { "id": "cmis-browser-target", "profile_kind": "target", "path": "schemas/cmis-browser-target.schema.json", "subject_type": "cmis-browser-binding-endpoint", "description": "Requires the target shape expected by the CMIS Browser Binding harness." } ] ``` Descriptor fields: - `id`: stable schema descriptor ID used in validation errors. - `profile_kind`: `target` or `assessment`. - `path`: JSON schema path relative to the extension root. - `subject_type`: optional target-profile selector. When present, the schema is applied only to targets with that `subject_type`. - `description`: optional authoring note. The core validates the generic guide-board schema first, then applies matching extension-owned schemas during `profile validate-*`, `plan`, and `run`. Extension schema paths must stay inside the extension root. The baseline validator intentionally supports the small JSON Schema subset used by guide-board contracts: `type`, `enum`, `required`, `properties`, `additionalProperties`, `items`, and `minItems`. ## Runner Entry Points Runner entry points currently support these kinds: - `python_module`: load a Python file from the extension directory and call a function. - `command`: execute a manifest-declared argv without shell expansion. The core writes a context JSON file and expects the command to print a JSON runner result to stdout. - `external`: declare an external harness that the baseline core cannot execute yet. Example: ```json { "id": "cmis-browser-preflight", "kind": "python_module", "module_path": "src/open_cmis_tck/preflight.py", "callable": "run", "command": null, "description": "Checks whether the CMIS Browser Binding endpoint is reachable." } ``` Command runner example: ```json { "id": "opencmis-tck", "kind": "command", "module_path": null, "callable": null, "command": ["python3", "runners/opencmis_tck.py", "--context", "{context_json}"], "description": "Checks dependency posture and prepares OpenCMIS TCK execution." } ``` Command placeholders: - `{context_json}`: generated context file for the current step. - `{root}`: repository root. - `{run_dir}`: current run directory. - `{extension_path}`: current extension directory. The command is executed with the extension directory as its working directory. The core does not use a shell for command runners. Runner context values are stable for bundled and external extensions: - `root`: the guide-board core root. - `extension_path`: the absolute path to the extension root. - `run_dir`: the current run output directory. - `plan`: the immutable run plan snapshot. ## Mapping Sets Mapping sets connect normalized evidence requirement refs to capability groups, controls, conformance classes, quality dimensions, or other assessment targets. Each mapping set lives under: ```text extensions//mappings/.json ``` and validates against: ```text docs/schemas/mapping-set.schema.json ``` The core does not embed domain policy. It only joins evidence `requirement_refs` to extension-owned mappings and writes normalized mapping records to: ```text runs//normalized/mappings.json ``` ## Evidence Request Sets Procedural and hybrid compliance extensions may include evidence request sets under: ```text evidence-requests/.json ``` These files validate against: ```text docs/schemas/evidence-request-set.schema.json ``` Evidence request sets are for collection guidance and review workflow. They should reference official requirements by stable IDs or user-held licensed material, but they must not redistribute proprietary standard text. A starter template lives at: ```text extensions/_template/evidence-request-set.json ``` See `docs/COMPLIANCE-EVIDENCE-PACKS.md` for the compliance-pack strategy. ## Expectations And Waivers Assessment profiles may reference expectation and waiver sets: ```json { "expectations_ref": "profiles/expectations/example.json", "waivers_ref": "profiles/waivers/example.json" } ``` Expectation sets mark known posture as expected. Waiver sets mark approved, time-bounded exceptions. Both are applied after findings are generated, and the assessment package records policy summary counts. ## Python Runner Contract A Python runner receives one context object and returns one result object. ```python def run(context: dict) -> dict: return { "result": "pass", "observations": ["Observed the expected condition."], "facts": {"key": "value"}, "artifact_refs": [], } ``` Context fields: - `root`: repository root path as a string. - `run_dir`: output run directory path as a string. - `run_id`: current run ID. - `plan`: full run plan snapshot. - `step`: the step being executed. - `target_profile`: target profile snapshot. - `assessment_profile`: assessment profile snapshot. - `extension_path`: extension directory path as a string. - `runner`: manifest runner declaration. Result fields: - `result`: one of the guide-board evidence result statuses. - `observations`: human-readable observations. - `facts`: structured facts extracted by the runner. - `artifact_refs`: references to raw artifacts written by the runner. Artifact refs must be paths relative to the run directory. After runner execution, the core fingerprints existing artifact refs into the assessment package `artifact_manifest`. If a Python runner raises an exception, the core converts that failure into `infrastructure_error` evidence so the assessment package remains complete. Preflight runners are gates. If an extension preflight returns `fail`, `blocked`, or `infrastructure_error`, downstream check groups for that extension are not executed; they receive `blocked` evidence with `blocked_reason: preflight_failed`. ## Normalizer Plug-ins Runners can keep returning guide-board-ready result objects directly. When a runner wraps a native harness or scanner that writes its own result format, the extension can add a normalizer descriptor: ```json { "id": "native-probe-normalizer", "kind": "python_module", "module_path": "normalizers/native_probe.py", "callable": "normalize", "runner_ref": "native-probe", "description": "Converts native runner output into guide-board evidence." } ``` Normalizers are declared in `extension.json` under `normalizers`. The original string shorthand remains valid for descriptive-only entries, but only descriptor objects are loaded and invoked by the core. The first supported normalizer kind is `python_module`. Its module path is resolved relative to the extension root and must stay inside that root. The callable receives one context object: - `root`: guide-board core root path as a string. - `extension_path`: extension root path as a string. - `run_dir`: output run directory path as a string. - `run_id`: current run ID. - `plan`: full run plan snapshot. - `step`: the step being normalized. - `target_profile`: target profile snapshot. - `assessment_profile`: assessment profile snapshot. - `normalizer`: manifest normalizer descriptor. - `runner_result`: the current runner-result object. A normalizer returns any subset of the runner-result fields: ```python def normalize(context: dict) -> dict: return { "result": "pass", "observations": ["Native result was normalized."], "facts": {"native_status": "ok"}, "artifact_refs": ["artifacts/native-result.json"], "requirement_refs": ["framework.requirement"], } ``` The core merges the normalizer output over the runner result: - `result` replaces the previous result. - `observations` are appended. - `facts` are merged. - `artifact_refs` and `requirement_refs` are deduplicated. - `normalizer_refs` is recorded in evidence facts when any normalizer runs. If a normalizer raises an exception, the step becomes `infrastructure_error` evidence and the run still produces its normal artifact set. The bundled `extensions/sdk-fixture` extension is the copyable reference path for profile schemas, a native-output runner, a normalizer, mappings, and fixture profiles. ## Result Statuses Initial statuses: - `pass` - `fail` - `warning` - `manual` - `not_applicable` - `skipped` - `expected_gap` - `waiver_applied` - `unsupported_by_design` - `infrastructure_error` - `blocked` - `unknown` ## Current Extension Examples - `sample-noop`: no runner, used to validate the core contracts. - `sdk-fixture`: compact SDK fixture covering profile schemas, runner output, normalizer invocation, mapping, and fixture profiles. - `open-cmis-tck`: provides a Python CMIS Browser Binding preflight runner and declares the future external OpenCMIS TCK runner. ## Next SDK Steps - Broaden normalizer examples as real external extensions adopt native harness result formats. - Add more extension-owned schema validation examples for assessment-specific domain constraints.