11 KiB
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:
extensions/<extension-id>/
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:
open-cmis-tck/
INTENT.md
extension.json
src/
mappings/
profiles/
runners/
workplans/
Pass external extension repos to the core CLI with:
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:
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 undermappings/<mapping-id>.json.certification_boundary: explicit statement of what the extension does not certify.
profile_schemas may use the original string shorthand for core schemas:
["target-profile", "assessment-profile"]
Extensions that need stricter domain-specific validation can add schema descriptors:
[
"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:targetorassessment.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 thatsubject_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:
{
"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:
{
"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:
extensions/<extension-id>/mappings/<mapping-id>.json
and validates against:
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:
runs/<run-id>/normalized/mappings.json
Evidence Request Sets
Procedural and hybrid compliance extensions may include evidence request sets under:
evidence-requests/<request-set-id>.json
These files validate against:
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:
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:
{
"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.
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:
{
"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:
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:
resultreplaces the previous result.observationsare appended.factsare merged.artifact_refsandrequirement_refsare deduplicated.normalizer_refsis 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:
passfailwarningmanualnot_applicableskippedexpected_gapwaiver_appliedunsupported_by_designinfrastructure_errorblockedunknown
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.