Skip to content

Assurance Forge capability matrix

This is the canonical record of what Assurance Forge can actually do, and it lives here rather than in the marketing site because this is the repository where capabilities are implemented, tested, and changed. The site (assurance-forge-site) is a downstream consumer: it renders feature-matrix.json, which is generated from this page.

For SACM 2.3 specification conformance — requirement-level obligations of the standard — see docs/sacm/sacm-conformance-matrix.md. That matrix answers "does the library implement the standard". This one answers "can a user do the thing". Rows here may cite a SACM23-* requirement ID to link the two.

For GSN v3 conformance, see docs/gsn/gsn-v3-conformance-matrix.md. It assesses model/import, authoring, rendering, validation, and interchange separately. A product capability below must not be interpreted as full GSN conformance when only some of those dimensions are implemented.

Why this is a gated artifact

A capability matrix nobody can check drifts into fiction. The version this replaced claimed SACM XMI import/export was "planned" and dialectic arguments were "planned" — both had shipped. Claims about what a safety-case tool supports are read by people deciding whether to trust it with a safety argument, so drift here is not cosmetic.

tools/features/check_feature_matrix.py runs as the feature_matrix_check CTest and enforces:

  1. IDs are unique and well-formed (AF-<AREA>-<NNN>), with a documented area code.
  2. Status values come from the vocabulary below.
  3. supported and prototype rows cite at least one code path.
  4. supported rows cite at least one test that resolves in the test tree.
  5. planned, candidate, and not-planned rows cite no tests — a planned row with tests is a status that reality has already overtaken.
  6. Every repo path cited in any column exists on disk.
  7. Every SACM23-* ID referenced exists in the SACM conformance matrix.
  8. feature-matrix.json is in sync with this page.

Regenerate the JSON after any edit:

python tools/features/export_feature_matrix.py
python tools/features/check_feature_matrix.py

Status vocabulary

Status Meaning Public roadmap equivalent
supported Available and intended for normal use. Backed by code and tests. Stable
prototype Usable, but format or UX is expected to change. Backed by code. Prototype 1 / 2
in-development Actively being built; not yet usable end to end. —
planned Committed to the roadmap, not started. Planned
candidate Under consideration. Scope and timing may change. Candidate
not-planned Deliberately out of scope. Recorded so it is not re-proposed. —

supported is a claim about a user-facing capability, not about specification coverage. A row may be supported while the underlying standard support is partial — say so in Notes rather than downgrading the row.

Areas

Code Area
STD Standards foundation (SACM)
GSN Core GSN
PAT GSN Pattern Extension
MOD GSN Modular Extension
ACP Assurance Claim Points and confidence arguments
DIA Dialectic arguments
METH Guided development methods
ENG Assurance case engineering
AI AI assistance
PLAT Platform and application

STD — Standards foundation

ID Capability Status Evidence Tests Notes
AF-STD-001 SACM 2.3 as the source of truth for assurance data supported libs/sacm/src, src/sacm_adapter/library_load.cpp tests/test_save_from_library.cpp ADR 0003. The app saves from the library model, not from projected UI state.
AF-STD-002 SACM 2.3 XMI import supported libs/sacm/src/io, src/sacm_adapter/library_load.cpp libs/sacm/tests, tests/test_sacm_roundtrip.cpp SACM23-XMI-001..004 verified. Both namespace dialects accepted on import.
AF-STD-003 SACM 2.3 XMI export supported libs/sacm/src/io, src/legacy_sacm/sacm_serializer.cpp tests/test_sacm_roundtrip.cpp Strict export normalizes to the pinned namespace; the pin is a project choice, not normative.
AF-STD-004 Import → export round-trip integrity supported libs/sacm/src/io, src/sacm_adapter/projection_diff.cpp tests/test_sacm_roundtrip.cpp, tests/test_projection_coverage.cpp SACM23-RT-001, SACM23-RT-002.
AF-STD-005 Argumentation package model supported libs/sacm/src, src/legacy_sacm/sacm_package_tree.cpp tests/test_sacm_package_tree.cpp, tests/test_package_commands.cpp SACM23-ARG-001..003, SACM23-PKG-001..003.
AF-STD-006 Artifact model supported libs/sacm/src, src/ui/register_views.cpp tests/test_sacm_roundtrip.cpp SACM23-ART-001. Surfaced to users through the evidence register (AF-ENG-008), which holds owner, type, maturity and controlled environment as vendor TaggedValues on the Artifact, and version, date and notes as its clause-12.7 provenance and Description.
AF-STD-007 Terminology model supported libs/sacm/src, src/core/terminology_package_service.cpp tests/test_terminology_package_service.cpp SACM23-TERM-001.
AF-STD-008 Assurance Case Package in-development libs/sacm/src, src/core/library_package_projection.cpp Package containment exists; a full ACP-level container with interfaces does not.
AF-STD-009 SACM model validation and diagnostics supported libs/sacm/src libs/sacm/tests SACM23-VAL-001, SACM23-VAL-002. Diagnostics catalog: docs/sacm/sacm-diagnostics-catalog.md.
AF-STD-010 Editing through SACM-native commands supported libs/sacm/src/commands, src/sacm_adapter/document_edit.cpp tests/test_sacm_library_edit.cpp, tests/test_library_primary_edit_flip.cpp SACM23-CMD-001..006. A claim carries one text, not two (ADR 0012): a Claim or ArgumentReasoning has exactly one clause-8.9 Description and that Description is its statement, edited as content. The separate note field these kinds used to offer is gone — the inspector no longer shows it, and an edit naming description on one is refused with a message naming content, by the command, by the proposal patch service, and by the edit seam. Refused rather than ignored: the note used to land in a second Description that the projection then dropped, so the edit reported success and vanished. Every other kind keeps its description, which genuinely is a note. Files written before this carry a redundant second Description; it is read past, and dropped when the file is next saved. No migration is provided — see the ADR's accepted consequences.
AF-STD-011 Refusal to lose unrepresentable SACM on edit supported src/core/commands/library_bridge.cpp tests/test_save_from_library.cpp, tests/test_projection_coverage.cpp SACM23-LIB-002. Every audited command applies through a native library seam, and a shape no seam expresses is refused with the tracked file byte-unchanged rather than rebuilt from a projection. The legacy compatibility bridge is deleted (#350 phases 1-4): ApplyLegacyOrRefuse refuses outright whenever a document is loaded, so there is no projection round trip left to lose anything and no document inventory left to sweep. Refusals a user can still meet are an ApplyProposal whose plan the seam cannot express and the seam-unsupported create fallbacks. The ProjectionCoverage.SACM23_LIB_002_BridgeRoundTrip* tests remain as regression pins on the projection itself, which audit replay and history reconstruction still read.
AF-STD-012 Mapped GSN v2.2 and legacy types project as GSN supported libs/sacm/src/io/name_tables.cpp libs/sacm/tests SACM23-COMPAT-001. Applies where a concrete SACM mapping exists. Abstract or contested types such as Choice and Away Context are preserved but cannot yet be rendered or edited as GSN.
AF-STD-013 Preserve original GSN type provenance through SACM round-trip supported libs/sacm/src/io/xmi_reader.cpp, libs/sacm/src/io/name_tables.cpp libs/sacm/tests/test_roundtrip.cpp SACM23-COMPAT-002. The source type is recorded as sacm.import.extensionType; Assumption and Justification retain their distinct roles. Save output uses SACM syntax, so this is provenance preservation rather than GSN-syntax round-trip.
AF-STD-014 Standalone SACM library, reusable outside the app supported libs/sacm/CMakeLists.txt, cmake/check_layer_gates.cmake cmake/check_layer_gates.cmake, libs/sacm/tests SACM23-LIB-001. The reverse gate scans quoted and angle includes and bans pugixml from public headers; it runs at configure time, so it cannot be skipped.
AF-STD-015 SACM 2.4 alignment candidate SACM 2.4 Beta 1 was published in September 2026 and is analysed in docs/sacm/sacm-2.4-beta1-impact.md. Not implementable yet: the beta defines no XMI namespace or schema, and its text and normative model disagree. metaClaim and the defeated literal are removed in it, which would affect AF-ACP- and AF-DIA-. SACM 2.3 remains the supported version.
AF-STD-016 SACM argument packages as a substrate for GSN modularity supported src/legacy_sacm/sacm_package_tree.cpp, src/core/library_package_projection.cpp tests/test_sacm_package_tree.cpp, tests/test_package_commands.cpp Packages exist and are editable, but this is infrastructure rather than support for GSN Module notation. See GSN3-MOD-001.
AF-STD-017 GSN-native export and syntax-preserving round-trip planned Compatibility saves preserve source-type provenance but normalize output to SACM syntax. GSN v3 has no published complete metamodel, so any project extension dialect must be clearly identified as non-normative. See GSN3-XMI-003.

GSN — Core GSN

ID Capability Status Evidence Tests Notes
AF-GSN-001 Goal supported src/core/element_factory.cpp, src/ui/gsn/gsn_shapes.cpp tests/test_element_factory.cpp
AF-GSN-002 Strategy supported src/core/element_factory.cpp, src/ui/gsn/gsn_shapes.cpp tests/test_element_factory.cpp Maps to SACM ArgumentReasoning; see docs/sacm/sacm-gsn-mapping.md.
AF-GSN-003 Solution supported src/core/element_factory.cpp, src/ui/gsn/gsn_shapes.cpp tests/test_element_factory.cpp
AF-GSN-004 Context supported src/core/element_factory.cpp, src/ui/gsn/gsn_shapes.cpp tests/test_element_factory.cpp Claim-versus-ArtifactReference typing is preserved and diagnosed, not guessed.
AF-GSN-005 Assumption supported src/core/element_factory.cpp, src/ui/gsn/gsn_shapes.cpp tests/test_element_factory.cpp
AF-GSN-006 Justification supported src/core/element_factory.cpp, src/ui/gsn/gsn_shapes.cpp tests/test_element_factory.cpp
AF-GSN-007 SupportedBy relationship supported src/ui/gsn/gsn_edge_renderer.cpp, src/core/tree_editing.cpp tests/test_tree_editing.cpp, tests/test_assurance_tree.cpp Endpoints are swapped against SACM AssertedInference on import.
AF-GSN-008 InContextOf relationship supported src/ui/gsn/gsn_edge_renderer.cpp, src/core/tree_editing.cpp tests/test_tree_editing.cpp Endpoints swapped against SACM AssertedContext.
AF-GSN-009 Undeveloped decorator supported src/ui/gsn/gsn_model.h, src/ui/gsn/gsn_shapes.cpp, src/ui/panels/element_panel.cpp, src/sacm_adapter/document_edit.cpp tests/test_assurance_tree.cpp, tests/test_library_primary_edit_flip.cpp Authoring is offered on Goals and Strategies. It is not offered on an Assumption or a Justification, and is refused if requested: SACM records the decorator in the same single assertionDeclaration that carries assumed/axiomatic, and GSN reaches those elements by InContextOf rather than SupportedBy, so they have no missing support to declare. Until this was fixed the inspector offered the toggle on every claim and reported success while the edit was discarded on reload, because the reader honours the legacy undeveloped attribute only when the declaration is still asserted. Reasoning and evidence: docs/sacm/sacm-gsn-mapping.md.
AF-GSN-012 Conventional GSN identifier generation for new elements supported src/core/element_factory.cpp, src/core/assurance_tree.cpp tests/test_element_factory.cpp New nodes receive a type-specific notation identifier, unique within the case, and render as identifier: name; storage identity remains independently stable.
AF-GSN-015 GSN identifier independent of storage identity supported src/core/element_factory.cpp, src/core/assurance_tree.cpp, src/ui/panels/element_panel.cpp, src/export/gsn_projection.cpp tests/test_element_factory.cpp, tests/test_element_edit_controller.cpp, tests/test_event_replayer.cpp, tests/test_gsn_svg_exporter.cpp, tests/test_xml_parser.cpp Imported opaque storage ids remain graph keys while the editable GSN identifier is persisted in assuranceForge.gsn.identifier, rendered on the canvas, and used as the SVG display label. The vendor tag is an Assurance Forge interchange convention, not a normative GSN XMI mapping.
AF-GSN-013 Core off-diagram element and reference notation planned Needed to split an SVG export for reports and printing across several files, marking where each continues. Decided in #454 as an export concern: nothing is stored in SACM and the canvas does not draw it. Not built yet. See GSN3-CORE-012.

PAT — GSN Pattern Extension

ID Capability Status Evidence Tests Notes
AF-PAT-001 Import, preserve, render and export the Uninstantiated decorator supported src/sacm_adapter/case_projection.cpp, src/ui/gsn/gsn_shapes.cpp, src/export/svg_writer.cpp tests/test_sacm_library_parallel_load.cpp, tests/test_layout.cpp, tests/test_gsn_svg_exporter.cpp Maps SACM isAbstract to the GSN hollow-triangle decorator. The tool cannot yet author or clear this state; see AF-PAT-009.
AF-PAT-002 Render the combined Undeveloped-and-Uninstantiated decorator supported src/ui/gsn/gsn_shapes.cpp, src/export/svg_writer.cpp tests/test_gsn_svg_exporter.cpp Draws the standard bisected marker when both flags are present; authoring Uninstantiated remains absent.
AF-PAT-003 Multiplicity decorator planned GSN isMany; no SACM feature exists to carry it.
AF-PAT-004 Optionality decorator planned GSN isOptional; no SACM feature exists to carry it.
AF-PAT-005 Pattern and Template argument model planned Covers the normative Pattern and Template concepts, separate from any product catalogue UI.
AF-PAT-006 Instantiation data reference planned
AF-PAT-007 Pattern Definition and Catalogue data planned Pattern Definition is normative in GSN v3 §1:3.4 even though the published GSN and SACM metamodels provide no class for it.
AF-PAT-008 Choice structural abstraction and cardinality planned Choice is part of the Pattern Extension. GSN v2.2 models the structural abstraction without attributes; v3 adds optional "m of n" cardinality, with SACM 2.4 draft Join bounds as the intended carrier.
AF-PAT-009 Author and edit Uninstantiated state planned Rendering and preservation are supported by AF-PAT-001; no editor command currently sets or clears SACM isAbstract.
AF-PAT-010 Pattern instantiation, related-pattern links and lifecycle state planned Covers instantiationOf, related patterns, and published/final states.

MOD — GSN Modular Extension

ID Capability Status Evidence Tests Notes
AF-MOD-001 GSN Module and Argument View notation planned SACM package infrastructure is supported by AF-STD-016, but the standard GSN Module symbol and Argument View are not drawn or editable.
AF-MOD-002 Cross-module relationships and module dependency view planned Includes module-level SupportedBy/InContextOf dependencies distinct from package containment.
AF-MOD-003 Away Goal supported src/core/element_factory.cpp, src/ui/gsn/gsn_shapes.cpp, src/export/svg_writer.cpp, src/core/problems/gsn_wellformedness.cpp tests/test_away_goal.cpp Cites a goal in another argument package, stored as SACM isCitation and citedElement and drawn with its source module by both renderers. See GSN3-MOD-003. Cites only goals in the same SACM file, and cannot be staged into a draft. Correction: the Away Goal menu offered every Claim in another module, assumptions and justifications included, and choosing one drew a goal where the other module states only an assumption; it now offers goals only (tests/test_away_assumption_justification.cpp). Away Assumption and Away Justification are AF-MOD-006; Away Solution and Away Context remain planned.
AF-MOD-004 Away Solution planned
AF-MOD-005 Away Context planned Metamodel is contested; decided 2026-07-20 to preserve rather than retype. See docs/sacm/sacm-gsn-mapping.md.
AF-MOD-006 Away Assumption and Away Justification supported src/core/element_factory.cpp, src/core/commands/element_commands.cpp, src/ui/element_context_menu.cpp, src/core/problems/gsn_wellformedness.cpp tests/test_away_assumption_justification.cpp, tests/test_event_replayer.cpp Cites an assumption or justification in another argument package from the Add menu, in context of the selected goal or strategy, stored as a SACM Claim declared assumed or as a justification, carrying isCitation and citedElement. Each menu offers only elements of its own kind from other modules. Both renderers draw the element with its source module and read its statement through the citation. A citation that resolves to nothing is reported under GSN3-MOD-006 or GSN3-MOD-007. Before this, an imported away assumption was classified as an away goal; it happened to render correctly, but only by accident. Same limits as AF-MOD-003: cites only within one SACM file, and cannot be staged into a draft. See GSN3-MOD-006 and GSN3-MOD-007.
AF-MOD-007 Module reference element planned
AF-MOD-008 Contract module and contract reference planned Maps to SACM ArgumentPackageBinding, not ArgumentPackage.
AF-MOD-009 Module interface planned SACM ArgumentPackageInterface exists; GSN v2.2 OCL disables it. See docs/sacm/sacm-gsn-metamodel-gaps.md row 10.
AF-MOD-010 Architecture view planned Diagrammatic; lands in the SACM 2.4 draft.
AF-MOD-011 Public decorator planned
AF-MOD-012 To-be-supported-by-contract decorator planned
AF-MOD-013 Cross-module context consistency and justification substitution planned Covers the normative consistency assertion around Away Goals and substituting an Away Goal for a Justification.
AF-MOD-014 Contract and circular module dependency validation planned Part of GSN modular semantics, separate from general SACM package validation.

ACP — Assurance Claim Points and confidence

ID Capability Status Evidence Tests Notes
AF-ACP-001 ACP on a SupportedBy relationship supported src/core/acp/assurance_claim_point.cpp, src/ui/gsn/gsn_acp_decorator.cpp tests/test_assurance_claim_point.cpp, tests/test_acp_editing.cpp
AF-ACP-002 ACP on an InContextOf relationship supported src/core/acp/acp_relationship_index.cpp, src/ui/gsn/gsn_acp_decorator.cpp tests/test_acp_relationship_index.cpp
AF-ACP-003 ACP identifiers supported src/core/acp/assurance_claim_point.cpp tests/test_assurance_claim_point.cpp Generates unique ACP<n> identifiers. Module-qualified display is a separate gap tracked by AF-ACP-009. Carried in a TaggedValue because the published GSN metamodel has no v3 ACP representation.
AF-ACP-004 ACP editing and management panel supported src/ui/panels/acp_panel.cpp, src/app/controllers tests/test_acp_controller.cpp, tests/test_acp_problem_sync.cpp
AF-ACP-005 ACP on a Solution or artefact-backed Context supported src/core/acp/acp_editing.cpp, src/ui/gsn/gsn_acp_decorator.cpp tests/test_acp_editing.cpp, tests/test_gsn_svg_exporter.cpp GSN v3 §1:5.2.2. Implemented for SACM ArtifactReference targets using vendor TaggedValues because SACM 2.3 has no metaClaim on those elements.
AF-ACP-008 Create and link a separate confidence argument tree supported src/core/acp/acp_editing.cpp, src/ui/panels/acp_panel.cpp tests/test_acp_editing.cpp, tests/test_acp_controller.cpp Creates a confidence ArgumentPackage and top Goal, links it to the ACP, and supports opening or selecting an existing confidence tree.
AF-ACP-009 Module-qualified ACP notation planned Render notation such as ACP1[Confidence] when the linked confidence argument lives in another module, as required by GSN v3 §1:5.2.3.
AF-ACP-010 Explicit risk-versus-confidence argument classification planned Confidence packages currently use an Assurance Forge purpose tag; a general GSN argument-type model and interchange representation remain absent.

DIA — Dialectic arguments

ID Capability Status Evidence Tests Notes
AF-DIA-001 Challenge relationship supported src/ui/gsn/gsn_model.h, src/ui/gsn/gsn_edge_renderer.cpp tests/test_dialectic_challenge.cpp Rests on SACM AssertedRelationship.isCounter, which GSN v2.2 OCL forbids — see docs/sacm/sacm-gsn-metamodel-gaps.md row 4.
AF-DIA-002 Counter-argument and counter-evidence rendering supported src/ui/gsn/gsn_edge_renderer.cpp, src/ui/gsn/gsn_layout.cpp tests/test_dialectic_challenge.cpp Dashed open-arrow challenge edge; side-stacked layout.
AF-DIA-003 Challenge targeting a relationship supported src/ui/gsn/gsn_model.h, src/ui/gsn/gsn_edge_renderer.cpp tests/test_dialectic_challenge.cpp Arrow lands on the target relationship's midpoint.
AF-DIA-004 Challenge-to-challenge supported src/ui/gsn/gsn_model.h tests/test_dialectic_challenge.cpp A challenge relationship is itself challengeable.
AF-DIA-005 Defeated decorator planned SACM AssertionDeclaration::defeated exists but SACM24-12 proposes removing it.
AF-DIA-006 Defeat on Strategy, Solution or Context planned Normatively required by GSN v3 §1:6.3.12 but structurally unrepresentable in SACM 2.3. Full GSN support therefore requires an explicit extension carrier outside the independent SACM library.
AF-DIA-007 In-doubt state for unresolved challenges planned A canvas attention cue exists, but no normative inDoubt model, edit or validation state exists.
AF-DIA-008 Challenge resolution and defeat propagation semantics planned Must distinguish an unresolved challenge from a successful defeater without reinterpreting it as support.

METH — Guided development methods

ID Capability Status Evidence Tests Notes
AF-METH-001 Problem detection and attention model supported src/core/problems/problems_manager.cpp, src/ui/panels/problems_panel.cpp tests/test_problems_manager.cpp, tests/test_problem_attention.cpp The substrate every quality check below reports through.
AF-METH-002 SCCG guideline catalog supported src/parser/sccg_dist_parser.cpp, src/parser/guidelines_parser.cpp, src/core/guideline_catalog.cpp tests/test_guideline_catalog.cpp, tests/test_guidelines_parser.cpp Safety Case Core Guidelines, pinned at 0.10.0 (schema_version 3.1.0) in the external/safety-case-core-guidelines submodule. 0.10.0 changed guideline content only: RD.2 covers confidence language and no longer operating scope, which is AR.6's, and the pass instruction gained a sentence (safety-case-core-guidelines#19); the contract, and therefore this loader, is unchanged. One loader reads one file: SccgDistParser loads dist/sccg.full.json, which SCCG declares sufficient on its own for review, authoring and retirement (since 0.9.0), and refuses a catalogue of any contract major but 3. It replaced two loaders over the same catalogue -- five per-concern JSON files and a sccg.full.yaml fallback -- which a test had to hold to agree; the YAML loader and the yaml-cpp dependency are gone, and the build copies the one file. What the per-guideline fields derive to (rule_id, category_id, reference ids, the profiles and data packages a guideline is reviewed in) is checked field by field against SCCG's own ai_rule_export.jsonl, so reading one file rather than five loses nothing. The loader reads the whole published tool contract: each package's role, element_role and per-field field_meanings, a profile's when_absent and review_passes, a pre-check's fires_when, a guideline's short_rule, distinguish_from, tool.markers, tool.thresholds and tool.repair, the selectable_elements and availability_states registries, when_unavailable, review_pass_instruction, retired_guidelines (AR.3, SU.9 and RD.6, each with the guidelines that now carry it; FindRetiredGuidelineById is deliberately separate from FindGuidelineById, so a retired id can never be reviewed against), the writing-time authoring_guidance subset, and the document block. AF_SCCG_DIST_DIR names an explicit distribution, ahead of the discovery order, so a caller can point the tool at a catalogue other than the one beside the executable -- comparing two SCCG versions, or asking what a proposed catalogue change would do. It is opt-in and silent when unset, authoritative when set -- one naming no directory fails the load rather than falling back to the shipped catalogue, which ran an evaluation meant for another catalogue against the shipped one -- and every review record states the sccg_catalog_path it actually loaded. The loader refuses a catalogue whose review profile does not require exactly one selected_element package, whose passes do not partition their profile exactly or include one with no id (dropping it could leave a profile with no passes, sent as one request), whose review_pass_instruction is present but not a string carrying {question} exactly once (omitted is a pre-0.9.0 catalogue; present and unusable is not), that has no retired_guidelines list (contract 3 requires one, empty or not), a guideline that is both retired and published, a retirement pointing at an unknown guideline, and a distinguish_from naming one. Correction (0.8.0): retired_guidelines shipped only in sccg.compact.json / sccg.full.json, which the runtime copy did not include, so the parser test read the submodule and passed while the application silently retired nothing; reading sccg.full.json alone removes that class of gap. Correction (release packaging): Windows and Linux release packages copied only the executable and the repository's sample data/ folder, never the catalogue beside the binary, so a released build found no SCCG catalogue; the release workflow now carries data/sccg/dist/sccg.full.json into the package (the macOS bundle already held it).
AF-METH-003 Manual and AI-assisted review workflow supported src/core/reviews/review_item_manager.cpp, src/ui/panels/review_panel.cpp tests/test_review_controller.cpp, tests/test_review_item.cpp
AF-METH-004 Review proposals with applyable patches supported src/core/reviews/review_proposal_patch_service.cpp tests/test_review_proposal_patch_service.cpp, tests/test_review_proposal.cpp
AF-METH-005 Guided top-down development method planned Structured authoring starting from goals.
AF-METH-006 Guided bottom-up development method planned Structured authoring starting from evidence.
AF-METH-007 Missing-support detection planned
AF-METH-008 Circular-argument detection supported src/core/problems/argument_cycles.cpp, src/app/structure_problem_sync.cpp tests/test_argument_cycles.cpp Reports every distinct loop in the support graph as an error, naming the strategy on it. InContextOf and GSN v3 challenges are excluded — both point "backwards" without being support, and following either would flag sound arguments.
AF-METH-009 Common GSN mistake detection planned Would report through AF-METH-001.
AF-METH-010 Requirement-traceable GSN v3 well-formedness validation supported src/core/problems/gsn_wellformedness.cpp, src/app/structure_problem_sync.cpp tests/test_gsn_wellformedness.cpp Every finding carries the GSN3-* requirement it enforces in the Problems panel's Guideline column, so a reader can take a diagnostic back to docs/gsn/gsn-v3-conformance-matrix.md and the standard instead of trusting the tool. Eight Core and Dialectic rules are checked: an unresolved relationship endpoint, an unresolved challenge target, the permitted element/relationship combinations (a Solution, Context, Assumption or Justification is a leaf), a Strategy wired as an end of an inference rather than as its reasoning, a non-Solution standing in the evidence position, two elements sharing a notation identifier, and an undeveloped decorator left on an element that has since been supported. The same connection rules now gate authoring, so the Add menu no longer offers a sub-argument under an Assumption — it did, and SACM could store the result. Not a complete GSN v3 validator: statement constraints (proposition and noun-phrase form) and the Pattern, Modular and Confidence extension rules are not checked. Content whose GSN role is undecidable — an unrecognized element type, a Context that may be either a Claim or an ArtifactReference — is passed over rather than guessed at, because a false structural error against an imported safety argument is worse than a missing one. Argument quality stays in the SCCG catalog (AF-AI-010), where a human resolves it. Repairs are AF-METH-011.
AF-METH-011 Repair a reported GSN well-formedness defect supported src/core/relationship_editing.cpp, src/app/controllers/element_edit_controller.cpp, src/app/structure_problem_sync.cpp, src/ui/gsn/gsn_acp_decorator.cpp, src/ui/panels/relationship_panel.cpp tests/test_gsn_repair.cpp Every finding AF-METH-010 raises carries a quick fix that clears it, and the repair differs by rule rather than deleting whatever is wrong: a broken endpoint drops just that reference (and says so if scrubbing left the relationship with nothing to relate, which removes it too); a connection GSN does not permit withdraws the relationship; a Strategy wired as an end of an inference moves into its reasoning slot, preserving an argument the author meant to make; a duplicated notation identifier is renumbered under its own prefix; a stale undeveloped decorator is cleared. This closed a gap that made the validator close to useless — it reported defects in an imported case that the tool had no way to correct, because PlanRemoval is node-shaped by construction and no affordance deleted a relationship at all. Relationship removal is now also a first-class edit, from the canvas edge menu and the relationship inspector. Every repair is an ordinary audited command with a replay branch, so it is undoable and attributed. No confirmation modal: these are single-relationship edits with undo behind them, unlike node removal which can take a subtree — but removing an inference that carried a Strategy detaches that Strategy and everything under it, and only the status message says so.

ENG — Assurance case engineering

ID Capability Status Evidence Tests Notes
AF-ENG-001 Audit register with a replayable event log supported src/core/audit/event_store.cpp, src/core/audit/audit_store.cpp tests/test_event_store_jsonl.cpp, tests/test_reconcile_audit_store.cpp
AF-ENG-002 Baselines supported src/core/audit/audit_baseline.cpp tests/test_audit_baseline.cpp
AF-ENG-003 Snapshots supported src/core/audit/audit_snapshot.cpp, src/core/audit/audit_manifest.cpp tests/test_audit_user_snapshot.cpp, tests/test_audit_manifest.cpp
AF-ENG-004 Report creation planned Depends on AF-GSN-013.
AF-ENG-005 SVG diagram export supported src/export/gsn_svg_exporter.cpp, src/export/svg_writer.cpp, src/export/gsn_projection.cpp, src/app/app_runtime_io.cpp tests/test_gsn_svg_exporter.cpp Core GSN nodes, SupportedBy/InContextOf/Challenges edges, and decorators (AF-ENG-015). Nodes and edges carry gsn-* CSS classes for restyling. Exports in the language the canvas is showing (AF-ENG-012): a secondary-language export takes each field's translation with a per-field fallback to the primary, and the language code lands in the file name so a Japanese export cannot silently overwrite the English one — that suffix is applied in app_runtime_io.cpp and is not covered by a test. Evidence carrying a recorded location is wrapped in an <a href> (AF-ENG-008), so an exported diagram is clickable: an absolute location becomes a file: URL that carries the author's local path into a file meant to be passed on, and a project-relative one is rebased onto exports/ and resolves only beside the project.
AF-ENG-006 History timeline and reconstruction supported src/core/audit/history_reconstruction.cpp, src/ui/panels/history_timeline_panel.cpp tests/test_history_reconstruction.cpp, tests/test_timeline_model_builder.cpp
AF-ENG-007 Undo with explicit boundaries supported src/core/audit/undo_boundary.cpp, src/core/audit/undo_resolver.cpp tests/test_undo_command.cpp
AF-ENG-021 Draft acceptance recorded as an audit transaction supported src/core/audit/audit_accept.cpp, src/core/commands/command_bus.cpp tests/test_audit_accept.cpp Accepting a working draft is one audited transaction naming the accept and carrying the accepted document and its provenance, not an unattributed overwrite. It becomes the trusted replay root, so later commands continue the chain from the accepted document rather than replaying through a superseded one; before this an accept read as an audit divergence. History reconstructs the state on either side of it (AF-ENG-006), it is an undo boundary (AF-ENG-007), and a replayed accept carrying no document is refused rather than applied blind.
AF-ENG-008 Evidence register supported src/core/registers/register_model.cpp, src/ui/register_views.cpp, src/sacm_adapter/document_edit.cpp, src/core/commands/element_commands.cpp, src/app/app_runtime.cpp tests/test_register_model.cpp, tests/test_evidence_location.cpp Row derivation is in core and tested; evidence nothing cites is listed rather than hidden. Assessments persist with the project (AF-ENG-016). Each row can be located in the argument (its package's canvas opens with the element selected), removed after the application's own removal confirmation, which lists what goes with it from the library's preview -- the AssertedEvidence links, a glossary term that cited it -- and always asks from the register, since a row is easier to hit by mistake than a selected node (while a working draft is active the removal is staged like any other draft edit). An ArtifactReference attached by AssertedContext is a GSN Context, not evidence, and is no longer listed -- it used to be reported as "evidence nothing cites". A glossary term citing the removed element as its origin loses the citation instead of blocking the removal. Each row can also be given a location -- a path or URL -- that opens with one click, a relative path resolving against the project root. The location is SACM data, not register metadata: it is written to the location of the Resource the ArtifactReference cites (clause 12.12), creating the Resource in the case's first ArtifactPackage when the reference cites none, through an audited command with a replay branch and, under a working draft, as the SetEvidenceLocation draft operation an MCP client can also send. The table scrolls horizontally with real initial widths and no frozen columns (the frozen block took most of the visible width, leaving the rest almost no room to scroll in). A location can be browsed for with a native file picker, recorded relative to the project root when the file is inside the project, and the Open button beside it opens what is recorded. On the GSN canvas, evidence with a recorded location carries a link badge that opens it, and the SVG export wraps that node in an <a href> carrying an external-link glyph, so an exported diagram is clickable in a browser: a project-relative location is rebased onto the exports/ folder the file is written into, an absolute path becomes a file: URL, and only http, https, file and mailto are emitted -- anything else (a javascript: or data: location) is dropped with a warning rather than shipped inside a document people open and pass on. The register's own columns are SACM-backed too: owner, type, maturity and controlled environment are TaggedValues (assuranceForge.evidence.*) on an Artifact the ArtifactReference cites, version and date are that Artifact's clause-12.7 provenance, and notes its Description -- created on the first write, cited beside the Resource that records where the evidence is, and written through the same audited command / draft operation / MCP path (SetEvidenceAttribute). Assessments an older project file holds are shown and edited there until the register's Move into SACM action imports them as one audited transaction; nothing is rewritten on open, because the SACM file is the safety argument. Under a working draft that import is staged as a draft edit while the project-file copy is released immediately, so discarding the draft and then saving loses those assessments; the mapping and the conflict refusal live in AppRuntime and are not yet covered by a test. The Recency free text becomes the Artifact's date. The register can also author: Add evidence creates a Solution with a statement, under a chosen claim or bare (registered before anything rests on it, which the register then lists as unlinked), and the Used by cell opens the claims resting on the evidence with an unlink for each and a picker to link another -- an AssertedEvidence added or withdrawn through audited commands (CreateEvidence, LinkEvidence, RemoveRelationship) or, under a working draft, the existing CreateSolution/AddSupportedBy/RemoveSupportedBy operations (the last of which now withdraws a solution's AssertedEvidence as well as a claim's inference). A link that is one end of a relationship carrying several sources is not withdrawn from the register; the canvas edge menu handles that. Bounded: opening a location -- the register's Open button and the canvas link badge -- is implemented for Windows only (ShellExecuteW); the other two supported platforms report that opening is unavailable. The scheme allowlist above is applied on the export path only: the in-application Open hands the recorded location to the shell as written, so a location that arrived inside an imported SACM file can reach any registered protocol handler without a confirmation. Not yet: the register is a derived table, not a SACM ArtifactPackage view in its own right; see docs/roadmap/public.md. The assuranceForge.evidence.* TaggedValues are an Assurance Forge convention -- another SACM tool preserves them but does not interpret them as register columns.
AF-ENG-009 CSE register supported src/core/registers/register_model.cpp, src/ui/register_views.cpp, src/sacm_adapter/document_edit.cpp, src/core/commands/element_commands.cpp, src/app/app_runtime.cpp tests/test_register_model.cpp, tests/test_evidence_location.cpp Claim/evidence pairings are derived in core and tested, with a pinned CSE id format. The assessment columns are SACM-backed: owners, criteria, status and notes are TaggedValues (assuranceForge.cse.*) on the AssertedEvidence that carries the support being judged, written through an audited command with a replay branch, a draft operation and MCP (SetCseAttribute), and read back by element reads. The subject is the SUPPORT, so where one AssertedEvidence carries several claim/evidence pairings those rows share one assessment and are marked as sharing it. Assessments an older project file holds stay there, shown and editable, until the register's Move into SACM action imports them as one audited transaction; two rows that share a relationship but disagree are refused rather than one silently overwriting the other. Under a working draft that import is staged as a draft edit while the project-file copy is released immediately, so discarding the draft and then saving loses those assessments; the pairing-to-relationship mapping and the disagreement refusal live in AppRuntime and are not yet covered by a test. Each row can be located in the argument -- the owning package's canvas opens with the element selected -- which replaced the claim-id and evidence-id columns, because an opaque storage id is not how a reader finds the support being judged. Not yet: the register is a derived table, not a SACM package view in its own right. The CSE<n> id is a display and legacy-store key only -- assessments in the document are keyed by the AssertedEvidence id -- so revisiting the id format no longer moves data.
AF-ENG-010 Terminology assist and controlled vocabulary supported src/core/terminology_scope_service.cpp, src/core/commands/terminology_commands.cpp, src/ui/panels/terminology_package_panel.cpp tests/test_terminology_scope_service.cpp, tests/test_terminology_commands.cpp, tests/test_save_from_library.cpp Documented in docs/features/terminology-assist.md. Glossary edits now apply straight to the SACM library rather than through the legacy conversion, so they work on cases the conversion could not represent (an ArgumentGroup, a nested ArgumentPackage) where every glossary edit used to be refused. Deleting a term that elements still reference now names them in the confirmation ("also removes 2 elements that reference it") and removes them on Yes, where before the reference was silently left pointing at nothing. The answer is recorded in the audit log, so a replay reproduces the decision rather than re-deciding it, and the status line reports the count actually removed. No cascade is offered for a file opened outside a project, where the edit path cannot honour it. A term still assigned to a category continues to block deleting that category, as before. Terms and categories are glossary content, not argument: they never render as GSN canvas nodes or navigator orphans (the tree builder excludes them, matching the SVG export's whitelist), and a term reaches the drawn argument only through a visible term context. While a working draft is open a glossary edit goes into the draft (ADR 0016, AF-AI-022): the terminology tab, the canvas's term detection and the terminology checks read the draft's package, the accepted glossary and its audit log are untouched until the accept, and the tab says where the edit will go before the first click. What the draft vocabulary cannot express -- the package's own name and description, deleting a package or a category, linking a term to an element as context -- is refused with a reason while a draft is open.
AF-ENG-011 Term usage detection and promotion to context supported src/core/terminology_term_usage.cpp, src/core/terminology_context_projection.cpp tests/test_terminology_context_projection.cpp, tests/test_terminology_visible_term_context.cpp
AF-ENG-012 Dual-language safety case content supported src/core/sacm_model.h, src/core/translation_review_store.cpp, src/sacm_adapter/case_projection.cpp tests/test_translation_review_sync.cpp, tests/test_multilingual_round_trip.cpp Per-element name_langs / content_langs maps carried through the model. A name carrying more than one language does not fit clause 8.6, so import parks the extra languages in a reserved sacm.import.name TaggedValue and the projection recombines them. That recombination used to let an overflow entry with no lang claim the primary's slot, replacing an English claim name with its translation on every projection — which changed what the claim asserted, made the canonical hash unstable, and left affected projects reporting an audit divergence that Reconcile could not clear. The overflow now never overwrites the primary language, and repeated project/serialize cycles are asserted to converge rather than accumulate.
AF-ENG-013 Deterministic model hashing for change detection supported src/core/audit/canonical_model_hash.cpp tests/test_canonical_model_hash.cpp
AF-ENG-014 Conformance tracking surfaced in the app candidate This matrix and the SACM one are repo artifacts today, not app features.
AF-ENG-016 Register assessments persist with the project supported src/app/controllers/register_controller.cpp, src/app/register_problem_sync.cpp, src/core/project_service.cpp tests/test_register_controller.cpp, tests/test_register_problem_sync.cpp This is the legacy path. New assessments are written into the SACM document (AF-ENG-008, AF-ENG-009); RegisterController reads, reports and discards what an older project file still holds, until the register's Move into SACM action imports it. Owners, criteria, assessment status and notes an older project file holds are read by RegisterController, saved to registers/register-assessments.af.json on project save, and tracked in the manifest. A file that fails to load is reported in the register tab, editing is disabled and saving is refused, so a bad read cannot overwrite what is on disk. Only rows a user edited are stored. An assessment whose CSE or evidence id leaves the argument is kept, never auto-deleted, and raised as a warning in the Problems panel carrying the stored content — the row is gone from the table, so the problem is the only place left to read it. Its "Discard assessment" quick fix drops it in memory; the file is only rewritten on save, so closing without saving takes it back.
AF-ENG-015 SVG export renders decorators and challenge edges supported src/export/gsn_projection.cpp, src/export/svg_writer.cpp tests/test_gsn_svg_exporter.cpp Undeveloped diamonds, ACP badges and dashed open-arrow Challenges edges now export. Previously a counter relationship projected as a SupportedBy edge, drawing counter-evidence as support for the claim it attacks.
AF-ENG-017 Automatic GSN diagram layout supported src/core/gsn_layout.cpp, src/ui/gsn/gsn_layout.cpp tests/test_layout.cpp Deterministic product behavior; not a normative Core GSN construct. No manual node positioning. Policy: docs/sacm/sacm-layout-policy.md.
AF-ENG-018 Pattern catalogue and guided instantiation workflow candidate Product workflow built on the normative Pattern Definition and Catalogue data tracked by AF-PAT-007.
AF-ENG-019 Confidence analysis model and store prototype src/core/confidence/confidence_store.cpp, src/ui/confidence_model.cpp tests/test_confidence_store.cpp, tests/test_confidence_model.cpp Assurance Forge analysis feature, not normative GSN confidence notation. Scoring model is not final.
AF-ENG-020 Confidence analysis panel prototype src/ui/panels/confidence_panel.cpp tests/test_confidence_problem_sync.cpp Product UI for AF-ENG-019; not part of the GSN Confidence Argument Extension.

AI — AI assistance

ID Capability Status Evidence Tests Notes
AF-AI-001 Provider-agnostic AI integration with explicit consent supported src/ai/ai_service.cpp, src/ai/ai_provider.h tests/test_ai_service.cpp ADR 0005. No data leaves the machine without user action.
AF-AI-002 AI profiles and settings supported src/ai/ai_settings.cpp tests/test_ai_settings.cpp
AF-AI-003 OpenAI-compatible provider supported src/ai/openai_provider.cpp, src/ai/ai_types.cpp tests/test_openai_provider.cpp Every successful response reports what it consumed (AiUsage: input, cached and cache-written input, output, and the reasoning share of output), or says it reported nothing, so a zero is never read as a free request. An exhausted account is told apart from a rate limit: OpenAI answers both with HTTP 429, and the tool used to report "Rate limit reached" to a user whose only fix was topping up the account (seen during a 300-run evaluation sweep, 2026-09-11); insufficient_quota / credit_balance_exhausted now report "The AI provider account has no credit left". The provider's own error code is kept on every failure.
AF-AI-004 Local secret storage for API keys supported src/ai/secret_store.cpp tests/test_ai_service.cpp, tests/test_secret_store.cpp Windows uses the Credential Manager, macOS uses Keychain Services, and Linux uses libsecret (GNOME Keyring, KWallet and anything else implementing the Secret Service API). Until #53 only Windows had an implementation, so the AI features did not work at all on the other two. test_secret_store.cpp round-trips save/overwrite/delete against the real platform store rather than a mock, which is what makes the Keychain and libsecret calls verified rather than merely written; it skips visibly where no store is reachable. All three backends are exercised in CI: Windows and macOS have a store by default, and the Linux job starts gnome-keyring under dbus-run-session so libsecret is executed rather than only compiled — the step fails if the round trip skips, since gtest exits 0 on a skip and a green step would otherwise prove only that a package installed. Bounded: libsecret is optional at build time, because no keyring API is guaranteed on Linux. A build without it refuses and names the package to install — deliberately, rather than falling back to a file, since a key on disk that the user believes is in a keyring is worse than a refusal. The two ways to be unavailable are reported differently, because they have different fixes: no libsecret in the build says install and rebuild, while a build that has it and finds no running keyring says start one. SecretStoreBackendName() reports which build this is. Release packages: up to 0.3.0-alpha.2 the release workflow's Linux job did not install libsecret-1-dev (CI's did), so the published Linux archive was a build without libsecret that refused to store a key — checked on 0.3.0-alpha.2, whose binary carries the "this build has no keyring support" message and no libsecret dependency. The job installs it from 0.3.0-alpha.3.
AF-AI-005 Background AI task execution supported src/ai/ai_task_runner.cpp tests/test_ai_task_runner.cpp
AF-AI-006 SCCG-guided AI element review supported src/review/sccg/sccg_review.cpp, src/review/sccg/sccg_review_preparation.cpp, src/review/sccg/sccg_review_passes.cpp, src/review/sccg/sccg_profile_selector.cpp, src/app/actions/ai_review_actions.cpp, src/app/actions/proposal_actions.cpp, src/app/controllers/ai_review_controller.cpp, src/app/app_runtime_ai.cpp, src/ui/gsn/gsn_badges.cpp tests/test_ai_claim_review.cpp, tests/test_sccg_review_preparation.cpp, tests/test_sccg_review_passes.cpp, tests/test_ai_review_controller.cpp, tests/test_ai_review_actions.cpp, tests/test_ui_state.cpp One AI Review action selects the unique SCCG profile for the selected element's element role — claim, strategy, evidence, assumption, justification, context, or challenge — which is the notation-neutral key SCCG publishes for a tool whose model is neither GSN nor CAE. The same key names the selected-element data package the element is sent in, looked up from the profile rather than hard-coded, so the profile chosen and the package sent cannot disagree. It reviews the complete integrated working draft, not only accepted SACM. Suggested corrections become independently reviewable SCCG draft groups linked to their finding and guideline. A finding may now propose a structural repair where that is what SCCG prescribes -- creating a Strategy for AR.2, a Solution for EV.1, a Context or Assumption for AR.3/AR.6/AR.7 -- staged as one group so a reviewer accepts the repair or none of it, rather than only rewording the reviewed element. The request now carries the case's own glossary and any prior findings on the elements under review (including findings on the parent and children it was shown, not only the selected element), and every absent data package says why it is absent -- the tool has no source, the case holds none, or it was deliberately withheld -- so a judgement bounded by what was not shown cannot read as a judgement that the data does not exist. A proposal may touch only the elements the review was shown (the profile's own data packages) and elements it creates itself; anything reaching further is refused and reported. A result is refused only if the elements it reviewed changed while the request ran -- an edit elsewhere in the draft, by the user or another contributor, no longer discards it. A successful no-findings result persists as a green check badge after the spinner stops. Selection fails closed if the catalog has no match or more than one match. An absent package now carries SCCG's own instruction for reviewing without it where the profile publishes one: evidence_review says to report the citation and control findings, state that sufficiency was not assessed, and not to report the absent basis as a finding against the argument, naming the eight guidelines (EV.5, EV.6, SU.3, SU.6–8, LF.5, LF.7) that cannot be assessed without it. That turns the degradation every evidence review already ran under from an improvised one into a declared one. The three absence states are SCCG's published availability_states rather than this tool's own names. Correction: until this row cited sccg_review_preparation.cpp, only three packages (PROJECT_GLOSSARY, CHANGE_HISTORY, USER_REVIEW_INTENT) ever reported empty; every other package the tool builds — PARENT, CHILDREN, DIRECT_CONTEXT, INHERITED_CONTEXT, STRATEGY, EVIDENCE_PATH, the selected-element packages — fell through a catch-all that declared them not_implemented. A root goal's absent parent and a claim's absent context were therefore reported as data the tool cannot produce, when the fact was that the argument does not contain them. Those two read in opposite directions: the first excuses the argument, the second is a finding against it. STANDARD_LINKS remains genuinely not_implemented — it has a required field (linked_requirements) this tool cannot fill. Under SCCG 0.8.0 an unavailable package silences nothing. EVIDENCE_BASIS has no source here and is reported not_implemented; 0.7.0's evidence_review when_absent statement turned that into eight unassessable guidelines (EV.5 measured 0 of 3 against evidence stating no sufficiency basis), and this row once recorded a workaround that sent the package available with every field empty. 0.8.0 fixed the cause (safety-case-core-guidelines#13) and defined a package with no required fields and nothing in it as empty, which made the workaround non-conforming, so it is gone. SCCG's when_unavailable rule and its meaning for each availability state now reach the model verbatim, replacing this tool's paraphrase. Under SCCG 0.9.0 the availability rule is uniform: a package is available when its required fields are present and at least one of its fields is populated, whatever it requires, so a claim's CHILDREN sent as {"child_elements": []} is reported empty rather than available-with-nothing (safety-case-core-guidelines#17). Correction: only the second half of that rule was checked, so a package with a required field left out but another filled counted as available; it is now reported not_implemented, naming the missing fields -- not empty, since the case may hold what was left out. 0.9.0's when_unavailable also lets the review rely on an empty package as a fact about the case -- a claim with no children has no path to evidence -- while not_implemented and withheld still tell it nothing. Every package uses SCCG's published field names, since 0.8.0 judges availability on them: USER_REVIEW_INTENT was sent as intent and CHANGE_HISTORY as review_items, neither of which the catalogue names, so both read as empty by its definition; history is now filed as prior_findings (AI) and review_comments (people), and a test checks every package the collector builds against the registry. A recorded finding citing a retired guideline keeps its id, with SCCG's redirect beside it. claim_review is sent as its four published review passes, concurrently, each carrying only its own guidelines and framed by SCCG's published review_pass_instruction sent verbatim with the pass's question substituted (0.9.0; this tool's own framing sentence is kept only for a catalogue that predates it; 0.10.0 added the rule that a defect a distinguish_from note assigns to a guideline outside the pass is not a finding in that pass, which reached every pass request with no code change), merged by MergeReviewPasses: a finding cited outside its pass is discarded and reported, and a failed pass makes the review incomplete — its findings recorded, an "AI review incomplete" item naming the pass, the outcome failed so no badge is earned. Measured on the probe corpus, splitting took 18 of 31 claim guidelines cited as intended to 26, none worse. It costs input: 195,338 bytes against 133,176 for one request on the Paper A top goal (+47%), because packages and instructions repeat per pass. Each guideline also carries SCCG's distinguish_from notes and its own tool.repair, and the response contract translates SCCG's repair vocabulary into operations once, replacing a hand-kept per-guideline list that named three guidelines SCCG has since retired. Steps 1–5 of the workflow now live in review::PrepareSccgReview, so the request the application sends and the request the evaluation harness (AF-AI-026) sends are assembled by one implementation. The response contract no longer asks for a severity. SCCG defines no severity concept for a guideline, so the field was this tool's own invention, and the prompt told the model it "should normally be warning" — which is what it got: 149 findings out of 149 marked warning across a 57-run measured sweep, a field carrying nothing a reviewer could rank or filter by. Severity is now assigned by the tool (every SCCG finding is an observation for a human to judge). Ranking uses two signals the model or the tool can actually establish: confidence, which the contract now defines against the supplied data rather than leaving undefined (and which does vary — 123 high to 26 medium over the same sweep), and pre-check corroboration via CorroboratingPrecheckIds, which names the deterministic checks that independently reached the same guideline.
AF-AI-007 MCP server — read a safety case from an external AI client supported src/mcp/server.cpp, src/mcp/tools.cpp, src/mcp/session.cpp, src/agent/read_operations.cpp, src/app/agent_request_handler.cpp, src/app/mcp_client_config.cpp tests/test_mcp_server.cpp, tests/test_mcp_modes.cpp, tests/test_mcp_reconnect.cpp, tests/test_agent_request_handler.cpp, tests/test_mcp_client_config.cpp Design: docs/features/mcp-server.md. assurance-forge-mcp speaks JSON-RPC 2.0 over stdio and reads a case (overview, search, element detail, GSN tree, argument files). It runs in one of two modes, re-evaluated on every call. Connected: Assurance Forge answers from the complete integrated working draft the user is looking at, including MCP, SCCG and human groups. Every case-content result names view, argument_file, workspace_id and working_revision, so an agent never mistakes unaccepted text for accepted SACM. Offline: no application is reachable, so the adapter reads accepted SACM from its own copy and reports view: accepted; it never opens .af/drafts and remains read-only. Both modes execute the same src/agent/ read implementations. The session heals without restarting the client: an offline session promotes itself when the application appears, a lost connection reconnects on the next call (the application restarting or switching projects is an ordinary event, not a dead session), an interrupted read is retried once after reconnecting, and an interrupted mutation is reported but never replayed — the application may have applied it before the connection broke. get_connection_status reports the current mode, application version and consent state without returning case content, so it works before consent is granted; initialize states the mode in its instructions. The session id survives reconnects, so draft-group ownership persists across an application restart. Consent is two gates: the ADR 0007 master flag (fails closed, re-read on every call, verified through the real process by cmake/run_mcp_smoke_test.cmake) and the ADR 0014 per-session access grant -- the user approves each session's access to the open project in the application, and ungranted operations are refused with project_access_pending. Independent of AF-AI-001..006: the layer gate forbids mcp/ from including ai/.
AF-AI-008 Local bridge between the MCP adapter and the running application supported src/bridge/protocol.cpp, src/bridge/transport.cpp, src/bridge/instance_registry.cpp, src/app/controllers/agent_bridge_controller.cpp, src/app/agent_request_handler.cpp tests/test_bridge_protocol.cpp, tests/test_bridge_transport.cpp, tests/test_instance_registry.cpp, tests/test_agent_bridge_controller.cpp, tests/test_agent_request_handler.cpp A versioned, user-private, message-framed connection: Windows named pipe, POSIX AF_UNIX socket, no TCP. The application publishes one instance record per running instance in the user's runtime directory (ADR 0014) — never in the project, which keeps af.proj under a single writer — carrying a fingerprint of the open project, not its path. The listener outlives project open/close/switch; a session bound to a project that is no longer active is refused with project_not_active rather than shown the new project, and a second instance opening an already-open project gets an advisory warning. Records are pruned by pid liveness. Two gates: the operating system's access control on the pipe (an explicit user-only DACL on Windows, 0600 on POSIX), and a 256-bit token from the record. Requests execute on the frame thread, so an operation sees exactly the model the user sees and needs no lock. Every frame carries a protocol version, and a mismatch between an old adapter and a newer application is reported as one message naming both versions. Both platform branches are compiled in CI; the Windows branch was additionally built with MinGW g++.
AF-AI-009 Propose changes to a safety case from an external AI client supported src/core/drafts/draft_document_store.cpp, src/core/drafts/draft_operation_apply.cpp, src/core/drafts/draft_workspace.cpp, src/core/drafts/draft_workspace_store.cpp, src/agent/draft_operations.cpp, src/app/agent_request_handler.cpp, src/app/app_runtime_project.cpp, src/mcp/tools.cpp tests/test_agent_draft_document.cpp, tests/test_agent_request_handler.cpp, tests/test_draft_workspace.cpp, tests/test_draft_operation_apply.cpp, tests/test_mcp_server.cpp, tests/test_mcp_modes.cpp, tests/test_patch_operation_parsing.cpp An agent contributes attributed change groups to the one persisted working draft for the argument currently open. It can begin, stage, replace, inspect, submit, remove and close its own groups, describe the combined draft, and poll revisioned events. Every modifying call carries expected_working_revision and expected_context_generation; a human, SCCG or other MCP mutation fails an old call with current_working_revision, and a project switch, fresh grant or revocation fails it as stale context, before anything is stored. Stable generated ids let a later call develop an element an earlier call created, and connected reads see that element because they use the working model. An MCP session may inspect every contribution but cannot mutate another contributor's group. Staging writes only .af/drafts recovery state — no accepted SACM, command or audit transaction — and survives restart with provenance and identities intact. Promotion stays the ordinary audited ApplyProposalCommand exposed only to the human UI. A registry-wide test proves there is no apply, accept or promote tool. Change-set tool names temporarily alias the group operations and no longer create private ChangeSetStore state. Staged operations are now applied to the draft SACM document (ADR 0016), through the same library seams the application uses on the accepted document, so an operation the model cannot hold is refused in the call that made it and named by its position in the batch — rather than staged, drawn on the canvas as pending, and refused at accept. Batches are atomic: a refused batch leaves the draft exactly as it was. created_element_ids now carries ids the document allocated, addressable immediately, because no later materialization can reallocate them. replace_change_group, remove_change_group and unstage_operations are refused against a document-backed draft and say what to do instead: they withdraw operations from a log, and reporting them as done while the draft still held the change would be the silent-drop defect in a different call. expected_working_revision now tracks the draft document, the counter that moves whenever anyone edits the argument — including the user, whose edits go into the same draft — so a client call computed before such an edit is refused. Support attaches by the relationship the child's GSN role requires, through the shared apply_attach_child seam: a Strategy is the reasoning of an inference rather than one of its ends (its inference is deferred until a sub-goal gives it a source, SACM clause 11.13), and a Solution attaches by AssertedEvidence — the only thing that distinguishes it from a Context, which is the same SACM type. Getting either wrong produced an argument that drew correctly and failed the GSN well-formedness check, or a Solution rendered with Context notation. An operation carrying a key the parser does not read is refused, naming the key and where its value belongs. It used to parse cleanly and report success: a CreateTerm sent with "definition" rather than "new_value" produced a term with an empty definition, so a glossary an agent had been asked to write arrived with every word undefined and nothing anywhere saying a definition had been dropped. A key the parser does read but whose value is the wrong type -- "new_value": 123 -- is refused for the same reason: it reached the applier as an empty string, so the operation applied nothing and still reported success.
AF-AI-010 SCCG guidance and checks for an external AI client supported src/mcp/guidance.cpp, src/core/sccg/staged_checks.cpp, src/core/guideline_catalog.cpp, src/agent/draft_operations.cpp tests/test_sccg_staged_checks.cpp, tests/test_mcp_server.cpp, tests/test_agent_request_handler.cpp, tests/test_draft_workspace.cpp Three mechanisms. Resources: sccg://guidelines publishes the catalog over MCP, opening with the title, purpose, version and licence from the catalog's own document block — the hardcoded heading fallback is gone, because since SCCG 0.7.0 the dist files the runtime loads carry that metadata too and no longer only the YAML fallback did — and resources/templates/list publishes sccg://guideline/{id} for one guideline at a time. The MCP executable carries its own data/sccg/dist copy, and the stdio smoke test drives resources/read and prompts/get through the real process in both consent modes — SCCG is the public corpus, not case content, so it is deliberately readable before consent. Prompts: draft_argument_from_standard, add_argumentation, restructure_case and translate_case each carry the guidance for that job, quoted from the catalog so prompt and guideline cannot drift (translate_case quotes CL.5, the qualifier rule its prose states informally); the workflow text tells agents to use revision-checked integrated draft groups, and to write every new element in each language the case is maintained in (AF-AI-019). Checks: every staging result returns SCCG and structural findings against the complete materialized draft, and the same findings are available through describe_working_draft. The mechanical set is the deliberately narrow, individually tested subset AF-AI-024 records — thirteen checks, each bound to the catalog's own check id and enumerated by core::sccg::ImplementedCheckIds(), which is the same list every result now reports. Most of SCCG is prose only a reader can judge, so this is not "SCCG compliance": findings are advisory and never block promotion. Every result now says which checks it could decide and states plainly that no findings is not conformance, and every finding carries the guideline's own wording plus its sccg://guideline/<id> resource, so an agent holds the rule rather than the tool's paraphrase of it. The authoring prompts no longer carry a hand-picked guideline list: each names the SCCG element roles its output produces, resolves those to review profiles through the catalog's published authoring_guidance.element_rules, and quotes their guidelines, so the criteria an agent writes to cannot drift from the criteria applied to it — the role-to-profile mapping was the last part of it held in code. All three mechanisms here land only when the client or user reaches for them; AF-AI-023 adds the channels that land unprompted, and docs/features/mcp-authoring-quality-plan.md is the plan for widening the whole surface.
AF-AI-011 Suggest where new argument belongs supported src/agent/placement.cpp tests/test_mcp_modes.cpp suggest_placement(topic) returns ranked goals and strategies with the path from the top goal, the sub-claims already there and the context in scope. Substring search says where a word appears, which is a different question from where an argument belongs; without this an agent guesses and attaches at the root. Ranked by term overlap and structural fit, not by understanding — the reply says so, and says to report that nothing fits rather than pick the best of a bad set. Only goals and strategies are offered as anchors.
AF-AI-012 Atomic re-parent for restructuring (MoveUnder) planned Restructuring is expressible today as remove-then-add supported-by pairs, so the capability exists, but a large restructure reads as a pile of unrelated operations and the intent is lost in the diff a reviewer has to approve. A MoveUnder patch operation would carry the intent atomically. Adding a PatchOperationType is backward compatible for reading existing audit logs. Not built.
AF-AI-013 Standards-clause traceability on claims candidate An agent applying a standard records which clause a claim answers in the claim description, because the element model has no citation field. That is a trace a human can read but not one a tool can follow, filter or check for coverage. A first-class citation would be a SACM modelling decision, not an MCP one.
AF-AI-014 One integrated working draft per argument file prototype src/core/drafts/draft_workspace.cpp, src/core/drafts/draft_workspace_store.cpp, src/core/drafts/draft_document_store.cpp, src/core/audit/audit_accept.cpp, src/agent/draft_operations.cpp, src/app/agent_request_handler.cpp, src/app/actions/ai_review_actions.cpp, src/app/actions/proposal_actions.cpp, src/app/app_runtime.cpp, src/app/app_runtime_project.cpp tests/test_draft_workspace.cpp, tests/test_agent_request_handler.cpp, tests/test_ai_review_actions.cpp, tests/test_audit_accept.cpp One persisted workspace per argument materializes ordered changes from MCP, SCCG AI review, and human draft editing over accepted SACM. The draft is projected through the same render passes as the accepted argument, so a term defined in a draft renders with its definition rather than as an undefined context node -- the draft view was a bare projection, so a term an agent had just defined showed on the canvas as though it had none until a restart re-ran the passes through load_file. Connected MCP reads and SCCG review both consume that combined model and write attributed groups back into it; revision checks prevent stale MCP calls from overwriting intervening contributors. Accepted SACM remains unchanged until human promotion. Kept at prototype while the merged workspace UX and end-to-end source combinations receive stability testing. The draft is now a SACM document rather than a list of operations (ADR 0016): contributors edit it through the library seams, the working view is its projection, what it changes is a comparison against the accepted document, and accept is one atomic write of that document over the argument. A draft is created by the first unaccepted change rather than when an argument is opened — one cloned per argument opened stopped descending from the argument it is compared against the moment the user edited the accepted document, and reported their own new elements as removals. While a draft exists the user's own edits go into it, for the same reason — argument edits and glossary edits alike, the latter expressed as the terminology operations an MCP client sends. The change-group ledger survives beside it recording who contributed what, and an argument the SACM library cannot load still falls back to the operation-staging path. The accept is recorded in the audit log as one AcceptWorkingDraft transaction whose WorkingDraftAccepted event carries the accepted document in full and the draft's provenance (groups, sources, guidelines, rationales). The event is replayable, so the history slider reconstructs the states on either side of an accept, and the accept is promoted to the trusted replay root with a snapshot at its own sequence, so the next project open verifies instead of reporting the accepted .sacm as a divergence (#409). Reported from a demo project built over MCP: everything the agent contributed was in the file and absent from the log, and the only remedy offered archived the history. The snapshot makes the accept an undo boundary — the draft it consumed is gone, so Ctrl+Z stops there and restore-from-history is the way back. Correction: while the argument drafted as a document, SCCG review did neither of the things this row claimed. It read the change-group materialization, which holds only what MCP recorded, so it judged accepted wording the user had already replaced in the draft; and its suggestions were staged into change groups only, so Accept wrote the document without them and then cleared their groups. Both now use the draft document. Provenance travels with the element as assuranceForge.draft.<contribution>.<field> tags written in the same all-or-nothing batch as the change, by MCP, SCCG review and the user alike, and stripped at accept (#409); a removed element carries none. See ADR 0009, ADR 0010, ADR 0016 and docs/architecture/integrated-draft-workspace-plan.md.
AF-AI-015 Dependency-aware selective promotion of draft changes prototype src/core/drafts/draft_dependency_graph.cpp, src/core/drafts/draft_promotion_service.cpp, src/app/app_runtime_project.cpp, src/ui/panels/element_panel.cpp tests/test_draft_workspace.cpp Accept a coherent change group without accepting the rest of the draft, with the dependency closure computed and shown first: a wording edit promotes alone, a new argument branch cannot promote without the strategy and relationships that make it meaningful. Remaining groups are rebased onto the prospective baseline and validated before anything is written, and promotion is refused before mutation if they cannot be. Promotion itself stays the unchanged audited ApplyProposalCommand, and undoing one restores the accepted baseline and the pre-promotion draft together (AF-AI-017). The deferred library re-derive bumps case_revision when it swaps the models, so the per-package canvas tab rebuilds: the dispatch bumps that counter a frame earlier while the old projection is still in place, and without a second bump the tab matched its own stamp and kept drawing text the accepted change had already replaced while the inspector showed the new text beside it. A promotion that fails after its marker is written clears the marker rather than leaving the workspace in Promoting, where it refuses editing, accepting and discarding until the application restarts — a failed accept must not take away every way to respond to it. The surviving groups are re-anchored to the argument the library produced, not to the one the patch predicted: the two agree for argument edits but not for terminology, where the seams stamp a gid a flat patch cannot know, and anchoring to the prediction declared every surviving group stale against the argument the user had just accepted into. Selection is per group from the Draft Changes panel (AF-AI-018) or per element from the Inspector's contribution list, with accept-all from the banner. Rejecting a group that others are built on now names them and offers the choice: reject them too, or keep them marked NeedsAttention — excluded from materialization, because left in they would fail to apply and block the whole draft, but retained in the workspace and recoverable by retargeting their operations. The audit transaction carries the promoted group ids, the contributing source labels, the guidelines served, the review items answered and each author's rationale, because accepting a draft consumes it and the log is then the only record of where the change came from. Kept at prototype until the release-gate scenario in #273 passes in the running application. Does not apply to a document-backed draft (ADR 0016), where accept is all-or-nothing: there is no selection of operations left to compute a closure over. Selective review inverts instead — the reviewer removes what they do not want from the draft and accepts what remains — and the gesture for doing so in the UI is not built yet, so a reviewer who wants only part of a document-backed draft currently has to edit it down by hand (#409).
AF-AI-016 Draft recovery across restart prototype src/core/drafts/draft_persistence.cpp, src/core/drafts/draft_workspace_store.cpp, src/core/project_service.cpp tests/test_draft_workspace.cpp Unaccepted work survives closing the application, stored under .af/drafts/ and reconstructed from the accepted baseline plus its change groups — never as SACM, which has no assertion state meaning "AI-proposed and not accepted". A stored draft whose base hash no longer matches the argument enters NeedsRebase and replays nothing. Promotion writes a pending marker before it touches accepted SACM, so a crash mid-promotion finalizes or cancels from the accepted-model hash rather than applying twice. .af/ is generated with a .gitignore so unaccepted AI-authored argument text does not reach a colleague through version control. Kept at prototype with AF-AI-014: recovery is only as trustworthy as the workspace lifecycle around it, which is still under stability testing.
AF-AI-017 Undoing an acceptance restores the draft it consumed prototype src/core/drafts/draft_persistence.cpp, src/core/drafts/draft_workspace_store.cpp, src/app/app_runtime_project.cpp, src/app/app_runtime_undo.cpp tests/test_draft_workspace.cpp Promotion is one boundary on the accepted undo stack, but a draft is deliberately not a command and has no entry on that stack. Undoing a promotion therefore took the change out of the accepted argument while the draft had already given it up — and where the promotion consumed the last group, the workspace was deleted with it, so the work was in neither place. Promotion now records the pre-promotion workspace under .af/draft-promotions/<transaction>.json, outside the per-argument draft directory the acceptance deletes, and undo puts the groups back with their provenance and generated identities, rebased onto the model the undo restored. Groups staged after the promotion are kept rather than replaced. A snapshot that cannot be read, or that belongs to another argument, refuses the undo instead of destroying the only copy of the work. Undo is now two stacks. While a draft holds unaccepted edits, Ctrl+Z reverses the last one and the accepted stack is untouched; it falls through to the accepted history once the draft has nothing left, which is what keeps a promotion undoable while later groups are still staged. The workspace revision still moves forward across a draft undo, so a token minted before it is not silently revalidated by content that happens to match, and next_sequence is monotonic so an undone group id never names a second group in one event log. A promotion clears the draft undo history: every entry below it describes groups the accepted argument now contains. The history is session state, not recovery state — the draft's content is persisted, so a restart recovers the work with nothing left to undo. Promotion snapshots are pruned against the audit undo boundary, the only rule that cannot delete one an undo could still reach.
AF-AI-018 Draft Changes panel — every unaccepted change, whatever wrote it prototype src/ui/panels/draft_changes_panel.cpp, src/app/areas/draft_changes_area.cpp, src/app/areas/feedback_dock_area.cpp, src/app/areas/canvas_history_overlay.cpp tests/test_draft_changes_panel.cpp One row per change group in the working draft, replacing the split proposal / change-set views that showed one source at a time and could not show a combination at all. Each row names who wrote it and in which session, its rationale, what it adds, changes and removes — with relationships counted separately from elements, because a changed support relationship can alter the meaning of an argument more than a reworded claim can — the guidelines it serves, the review items it answers, what it depends on, findings against what it would produce, and whether it can be accepted right now. Promotability is answered by planning the promotion against the materialized working model rather than guessed at, and a row that cannot be accepted says so on the row rather than in the status bar. Accepting names what else it would accept before the button is pressed. Selecting a row takes the user to its first changed element in whichever view can show it: the GSN canvas for argument, the terminology view for a term that is already in the accepted glossary, and nowhere at all for a term this draft created — that view reads accepted terminology, so going there would report the term missing, and the row's own glossary lines are where it is readable. A glossary group additionally lists each staged term with its definition, categories and source in full on the row — a term is deliberately not a GSN node (AF-AI-022), so there is no canvas rendering beside the row to read it from. A whole draft held back — stale, promoting, or unmaterializable — is explained once above the list, because in that state no single group is the explanation. An accept that refused is explained the same way, on the banner beside the button that appeared to do nothing and above the list, until the draft changes: previously the only report was the status bar, which is one line and truncated the sentence before the reason, so a user who pressed Accept all was left with a banner still counting unaccepted changes and nothing on screen saying why. Prototype: not yet seen in the running application against a real multi-source draft, which is the #273 release gate.
AF-AI-019 Bilingual argument from an external AI client prototype src/core/reviews/review_proposal.cpp, src/core/reviews/review_proposal_patch_service.cpp, src/core/reviews/review_proposal_plan.cpp, src/agent/change_operations.cpp, src/agent/read_operations.cpp, src/core/drafts/draft_promotion_service.cpp, src/mcp/tools.cpp, src/mcp/guidance.cpp, src/app/app_runtime_project.cpp tests/test_review_proposal_patch_service.cpp, tests/test_draft_workspace.cpp, tests/test_agent_request_handler.cpp, tests/test_change_set_acceptance.cpp An agent states each element in every language the case is maintained in, within the one operation that creates it: text/new_value carries the primary language and translations carries the rest, so a reviewer accepts a bilingual claim or none of it and no group promotes half-translated. An UpdateElementText carrying only translations revises those languages and leaves the primary text alone, which is what makes translating an existing argument safe — translating a safety case must not edit it. A Create* with translations and no primary text is refused: that element would render empty for every reader who has not switched languages. Reads report translated_languages per element and the text under translations, and find_elements matches either language. The same vocabulary unblocked human secondary-language edits while a draft is active, which were previously refused outright. Translations from a non-human source arrive flagged TranslationReviewNeeded on promotion (AF-ENG-012): accepting the argument is not the same as establishing that the Japanese says what the English says. The element semantic hash now covers secondary-language text, so a proposal written against an untranslated element no longer looks current after someone translates it. A bilingual group could be staged, shown and planned but not accepted: the proposal planner declined a non-primary-language name as unrepresentable, three hours before the adapter gained the reserved sacm.import.name write that carries exactly that (AF-ENG-012, SACM23-LIB-002), and the rationale was never revisited. With the compatibility path since removed, Accept All refused an entire 64-operation draft over one translated goal name — a person could type that same name into the inspector and it saved. Acceptance of a bilingual group, including a created element's translations, is now asserted end to end through the promotion plan, the preflight and the saved file. Prototype: the write path still rides the draft workspace under stability testing (AF-AI-014).
AF-AI-020 Assurance claim points readable from an external AI client supported src/agent/read_operations.cpp, src/mcp/tools.cpp, src/app/agent_request_handler.cpp, src/mcp/session.cpp tests/test_agent_acp_reads.cpp, tests/test_assurance_claim_point.cpp list_assurance_claim_points serializes the projected ACP records: what each one annotates — an element such as a Solution, or a SupportedBy/InContextOf relationship — and how it is resolved: inline text, or a confidence argument whose claim, package and top-goal ids are followable with get_element; an ACP with no resolution reports instantiated: false, the state the ACP panel warns about. get_element carries the ACPs on the element and on the relationships touching it, relationship-borne entries naming the relationship they ride. Available connected (working-draft view) and offline. Read-only, deliberately: the patch vocabulary has no ACP operation, and extending it is an ADR 0009 vocabulary decision with its own GSN review — authoring stays in the application (AF-ACP-001..008). ACPs seen through the working draft come from the accepted baseline, since no draft operation can create one.
AF-AI-021 Projectless MCP sessions with runtime project binding prototype src/mcp/session.cpp, src/mcp/main.cpp, src/app/controllers/agent_bridge_controller.cpp, src/app/areas/modal_host.cpp tests/test_mcp_dynamic.cpp, tests/test_agent_bridge_controller.cpp Launched with no project argument, the adapter initializes without a running application, discovers the single running instance at call time, and connects unbound (ADR 0014). Access is granted per session: the first project operation (or request_project_access) raises an in-app request — client label, project, Allow while open / Deny — and is refused with project_access_pending until the user answers; this applies to --project sessions too. Grants are keyed by session id, survive a reconnect to the same instance, and end on deny, revoke, project close/switch, MCP disable, or restart; a re-granted session still owns its draft groups. Multiple running instances are never auto-selected. --offline-project <path> is the deliberate read-only escape hatch that never connects. Prototype: the single-instance path is complete and tested end to end through the real adapter process; the setup UX is not. With more than one application running the session refuses and names the fix rather than choosing, and the client configuration Preferences copies always pins --project <the open project>, so the projectless connect-once setup this row is about still has to be written by hand -- both are expected to change how a user first connects. (Context envelopes and generation checks shipped with AF-AI-009; rebinding is the fresh grant raised after a project switch.)
AF-AI-022 Terminology read and management from an external AI client prototype src/agent/read_operations.cpp, src/agent/change_operations.cpp, src/core/reviews/review_proposal_patch_service.cpp, src/core/reviews/review_proposal_plan.cpp, src/sacm_adapter/document_edit.cpp, src/core/commands/proposal_commands.cpp, src/mcp/tools.cpp, src/core/drafts/draft_operation_apply.cpp, src/app/areas/workbench_area.cpp, src/app/actions/terminology_actions.cpp, src/ui/panels/terminology_package_panel.cpp tests/test_mcp_server.cpp, tests/test_agent_request_handler.cpp, tests/test_review_proposal_patch_service.cpp, tests/test_change_set_acceptance.cpp, tests/test_draft_operation_apply.cpp, tests/test_term_definition_survives_save.cpp, tests/test_working_glossary.cpp list_terms returns every term's value, name, definition, categories, external reference and origin in one call, together with the categories the case defines, connected and offline; terms and categories also appear in get_case_overview counts, find_elements and get_element. CreateTerm, UpdateTerm, RemoveTerm, CreateCategory and UpdateCategory stage through the same revision-checked change groups as argument edits and promote through the same audited ApplyProposalCommand; accepting the first term or category of a case with no glossary creates the containing terminologyPackage rather than refusing. UpdateTerm fields are value, definition, name, category (space-separated ids), external_reference and origin — the last three are what answer the terminology check's "no category" and "no external reference/source" findings, which an agent could previously read but not fix. Each field is written by its own seam so classifying a term cannot rewrite its definition or drop the translations of it; an unresolvable category or origin is refused at staging, not at acceptance. Definitions may carry translations; a term's value is a single string (SACM 10.11) and staging refuses a translated one with an explanation. Addresses SCCG CL.5 by defining a bounding term once instead of repeating it as free text. Bounded: removing a category, and associating a term with an element as a visible context, stay in-application (a category delete has cascade semantics needing a confirmation this surface cannot raise); created terms and categories land in the case's first terminology package; removing a term an argument package references is refused at acceptance by the library's cross-package delete guard. Prototype with AF-AI-014: the write path rides the draft workspace, which is still under stability testing; the read path is exercised through the real MCP server process. A CreateTerm with no definition is refused, on the staging path and the review path alike, and list_terms reports how many existing terms have none. Reported twice from real sessions: an agent staged a whole glossary, set the category and external reference the guidance names as checked, and left every definition empty — because nothing asked for one. The terms reached the accepted argument as words with nothing beside them, which reads as a defined glossary and is not. The definition itself was never the broken part: it stores and serializes through both the create and the UpdateTerm path, and there are now tests pinning that. A term is matched against element text by its value, so the schema now says the value must be the string exactly as it is written in the argument — EPB, not EPB (Electronic Parking Brake) — with the expansion in the definition, and list_terms marks every term whose value appears nowhere in the case. All four abbreviations in a reported session were staged with the expansion inside the value, so they bound nothing and every occurrence read as undefined. The working glossary is what the application shows (ADR 0016): the terminology tab, the canvas's term detection and the terminology checks read the draft document's package while it differs from the accepted argument, every row the draft added or changed is badged draft with the fields that differ, a notice above the table counts the unaccepted glossary changes, and a glossary the draft itself created is shown although the accepted package has none. Reported from a demo: a definition an MCP client revised was invisible until a restart, because every terminology surface read the accepted package. The user's glossary edits go into the draft too: while a draft document exists, a term or category added, changed or deleted in the terminology tab (and a term defined from the canvas) is applied to the draft document as the same CreateTerm / UpdateTerm / RemoveTerm / CreateCategory / UpdateCategory operations a client sends, one per changed field, through the same seams — so a human edit is accepted or refused exactly as a client's is, the accepted glossary and its audit log stay untouched until the accept, and the tab says where the edit goes before the first click. Previously those edits were refused outright, because they wrote to the accepted document the draft no longer descended from. Bounded: what the vocabulary cannot express — the package's own name and description, deleting a package or a category, linking a term to an element as context — is still refused with a reason while a draft is open, and a new term or category lands in the case's first glossary.
AF-AI-023 Authoring doctrine delivered in every MCP session supported src/mcp/guidance.cpp, src/mcp/server.cpp, src/mcp/tools.cpp tests/test_mcp_server.cpp Phase 1 of docs/features/mcp-authoring-quality-plan.md. The SCCG prompts and resources (AF-AI-010) land only when the user asks for them; this row is the answer for the user who types "add an argument that braking is safe" and says nothing about SCCG. The authoring doctrine — one rule per line, each naming the guideline it condenses — travels in the three channels that reach the model unprompted. It is rendered from the catalog, not written here: SCCG 0.7.0 publishes authoring_guidance.core_rules, the eighteen-guideline subset a tool should deliver while an author is writing rather than when a review is run, each with a one-line short_rule and a recorded reason for inclusion. The fifteen lines this used to maintain by hand are gone, and with them the "every SCCG family is represented" property that comment asserted on its own authority — a test now checks it against the published subset. The rendering follows the file's own usage: from short_rule, citing the id, without paraphrase, and carrying SCCG's caveat that the subset is a delivery subset and not a reduced standard. The three channels are: initialize.instructions (alongside the connection-mode statement), an authoring_guidance field on the four pre-write reads (get_case_overview, get_argument_tree, suggest_placement, get_draft_status — the looped reads get_element and find_elements stay lean deliberately, so the guidance is not noise a model learns to skip), and the operation schema's text description, which is in context at the exact moment a claim's words are generated. A test resolves every id the doctrine names through the server's own sccg://guideline/<id> resource, so a condensation naming a guideline the catalog no longer has fails rather than quietly lying, and a second holds every rendered line to the published short_rule it must quote; the stdio smoke test proves the doctrine crosses the shipped binary's transport in both consent modes (the doctrine is the public house rules, not case content). The prompt-quoted sets also widened — the SU, LF and RD families were previously quoted in no prompt at all.
AF-AI-024 Mechanical SCCG checks bound to the published catalog supported src/core/sccg/staged_checks.cpp, src/app/areas/staged_finding_text.cpp tests/test_sccg_staged_checks.cpp, tests/test_staged_finding_text.cpp Phase 2 of docs/features/mcp-authoring-quality-plan.md. Fifteen checks, up from four: all five of SCCG's published deterministic pre-checks now run and are reported by their registry id (check-explicit-strategy, check-evidence-trace, check-evidence-citation-precision, check-evidence-control-attributes, check-evidence-state-fixed), plus EV.1 (unsupported claim not marked undeveloped), AR.2 (a decomposition whose sub-claims carry no reasoning step -- the catalog's check-explicit-strategy, advisory because GSN permits goal-to-goal support), AR.1 (solution with children; strategy developing into nothing; support cycles), CL.5 (unbounded qualifier), and batch 1 of the widening — CL.2 (two properties joined by a conjunction), CL.6 (lifecycle steps chained in one claim), RD.1 (reasoning smuggled into claim text), RD.4 (promotional language), EV.7 (evidence with no owner, version, date or status), EV.8 (mutable source cited with nothing fixing its state), LF.3 (absence of discovered evidence offered as support), and — new with SCCG 0.7.0 — CL.4 (a qualifier or hedge two reviewers can read differently) and CL.3 (a claim past the published word count, or a topic list written as prose). Every check binds to the catalog's suggested_checks id, carried on the finding and on the MCP wire. The word lists and thresholds are now read from the catalog rather than held here: SCCG 0.7.0 publishes tool.markers for 27 guidelines with three effects — candidate raises a signal, suppress cancels one (CL.5 no longer fires on a claim that states its bound), and expected inverts it, its absence being the signal — and tool.thresholds for CL.3 and LF.6. Two tools matching different words are not running the same check, and the hand-derived lists this replaced were narrower than the guideline in ways nothing recorded. ImplementedCheckIds() is now conditional on the catalog: a lexical check whose word list could not be loaded is not named, because an agent reading an empty findings array must be able to tell "found nothing" from "never ran". Each lexical check must fire on its guideline's own bad example and stay silent on the good one, read from the catalog at test time. A drift test holds each embedded statement to be a prefix of the catalog's, over a model built to trip every check the tool says it implements — it reached seven of thirteen before, so six quotes were never compared and one of them was a paraphrase. All lexical findings are advisory — the reviewer judges the words, the check only points — and the panels translate them by check id (the structured-findings seam), so the widening reaches the human review surfaces and MCP clients from one implementation. Partial catalog coverage: the remaining candidates are CL.1, EV.3, LF.1's textual half, LF.6, AR.8 and SU.2. CL.3 and CL.4 have left this list because 0.7.0 published the threshold and the word list they were waiting for. LF.6 has its threshold now and is still deferred for a different reason: the check needs a definition of "a figure" in claim text that a significant-digit count alone does not give, and a false precision finding against a correctly stated number is the kind that teaches a reviewer to stop reading. CL.6 fires on two lifecycle verbs joined by a conjunction where SCCG's published condition is two anywhere in the claim — a narrowing calibration SCCG permits, kept because it is what lets the finding quote the chain it objected to. tool.repair, published for all 48 guidelines, is read but not yet used to seed proposals.
AF-AI-025 Pre-flight rehearsal and submit-time refusal of problem findings supported src/agent/draft_operations.cpp, src/mcp/tools.cpp, src/mcp/session.cpp, src/core/drafts/draft_workspace_store.cpp, src/core/drafts/draft_persistence.cpp, src/ui/panels/draft_changes_panel.cpp tests/test_agent_request_handler.cpp, tests/test_mcp_modes.cpp, tests/test_draft_workspace.cpp Phase 3 of docs/features/mcp-authoring-quality-plan.md — where the guidance gains teeth. check_operations rehearses operations on a copy of the integrated draft: the same validation and findings stage_operations would return, with nothing stored, no revision moved, no element ids allocated, and nothing drawn on the user's canvas — an agent iterates privately until its work is clean, then stages once. It is the one draft-vocabulary tool that works offline, because a rehearsal against the accepted copy is a read; the byte-identical sweep covers it. Submit-time refusal: submit_change_group refuses while problem-severity findings (SCCG Problem checks and GSN well-formedness findings) stand against the group, naming each one in problem_findings. The gate sits at submit, not at staging — staging is deliberately incremental and every intermediate shape is legitimately unfinished, but submit is the author declaring itself done. The explicit escape is acknowledge_findings: true, which submits anyway and records the acknowledged findings on the group — persisted, surviving restart, shown to the reviewer on the Draft Changes row, and cleared when the group's operations change, because the waved-through findings described a different shape. Advisory findings never refuse anything, deliberately: gating on them would train agents to acknowledge reflexively, which would spend the gate. Promotion authority is untouched — the human reviewer's decision is not gated by anything here.
AF-AI-026 Offline SCCG review evaluation harness supported src/eval/sccg_review_eval.cpp, src/eval/sccg_review_eval_options.cpp, src/review/sccg/sccg_review_preparation.cpp, src/review/sccg/sccg_review_passes.cpp tests/test_sccg_review_preparation.cpp, tests/test_sccg_review_passes.cpp, tests/test_sccg_review_eval_options.cpp af-sccg-review-eval, a headless binary that runs the SCCG review method over a project from the command line: the same profile selection, data packages, pre-checks, request and response validator as the in-app review, with no window. It writes one JSON record per run — SCCG version, profile, guidelines carried, packages supplied and the availability state of each one absent, pre-check verdicts, model, prompt hash, elapsed time, and every finding with its cited guideline — which is the review record the SCCG method says a tool should retain. --runs repeats a review so a guideline that fires intermittently can be told from one that does not fire; --model varies the model without touching saved settings; --dry-run assembles and records the request without calling a provider, so profile selection, package availability and prompt content can be checked at no cost. It exists because the review method was previously reachable only by rendering a frame, which makes guideline coverage over a whole argument impossible to measure. It is not a gate: it calls a paid external provider and its output depends on a model, so it is never a CTest — review::PrepareSccgReview is what the tests cover, and this turns the prepared request into a recorded run. Review passes are sent concurrently and merged exactly as the application does; each record lists every pass with its guidelines, prompt hash and size, latency, outcome and raw response, plus any finding discarded for being cited outside its pass. --single-request sends the whole profile as one request instead, so the two can be compared on the same material; the application never does. Cost controls for sweeps (none of which the application uses): --service-tier flex sends OpenAI's slower tier at batch prices with a 900 s timeout; a refusal made before any work -- a rate limit, flex having no capacity, an overloaded server (HTTP 500/502/503) -- is retried with exponential backoff and the attempts are recorded, while a timeout (possibly billed) and an exhausted account are not. Every run record carries token usage per pass and per run, including cached input and reasoning, and the tier that actually served each request, so the cost of a sweep is in its records rather than only on the provider's invoice. --first-run n numbers an invocation's runs from n, so a three-run sweep can be extended to five without paying for the first three again; a dry run from both builds confirms the requests are byte-identical before runs from two invocations are counted together, and a consensus record names the first_run and last_run it covers. The command line is tested: every number is refused when malformed or out of range -- --timeout 0 used to switch the request deadline off, and --runs 4294967297 read as 1 -- and a --first-run/--runs pair whose last run number would not fit an int is refused rather than running nothing. A record that cannot be written counts as a failure, and an output directory that cannot be created stops the harness before any paid call. Every per-run finding carries the model's confidence, not only the consensus. user_prompt_bytes is the size of the text user_prompt_sha256 hashes, separators included; the passes' own prompts sum to passes_prompt_bytes. A run's usage states passes_reporting and whether it is complete, since a pass that reported none leaves the total short. tool_build is stamped at build time rather than configure time, so a commit in the same build tree no longer leaves every record naming the previous build. The retry policy itself has no deterministic test yet.
AF-AI-027 Consensus review over repeated runs supported src/review/sccg/sccg_review_consensus.cpp, src/eval/sccg_review_eval.cpp tests/test_sccg_review_consensus.cpp Runs the same unchanged review request k times and groups what comes back by the guideline it cites, reporting each finding with the number of runs that produced it, whether they were unanimous, every run's wording, and any deterministic pre-check that independently reached the same guideline. A floor (--consensus m) separates well-supported findings from weak ones; findings below it are kept and reported separately rather than dropped, because a finding one run of three raised is weak evidence and not none. Counting rules that matter: one vote per guideline per run (a run that raised a guideline three times has found one thing worth saying three times), the same guideline on different elements is different findings, and agreement is counted against the runs that succeeded — 2 of 2 when the third run errored is not 3 of 3. This does not make review deterministic and must not be described as doing so; it makes non-determinism visible. It exists because the provider offers no way to make a review repeatable: measured directly, gpt-5.6-sol rejects temperature ("not supported with this model") and seed ("unknown parameter"), gpt-5.5 rejects temperature too, and only gpt-5.4 accepts it — so the newer the reasoning model, the less sampling control there is. Currently exposed through the evaluation harness; the in-app review still runs once. A run in which any review pass failed counts as a failed run, not as one that cited nothing: its missing pass never asked about the guidelines it owns, and counting them as "not cited" would put a false disagreement into the consensus.
AF-AI-028 Sampling controls in AI settings supported src/ai/ai_types.h, src/ai/openai_provider.cpp, src/eval/sccg_review_eval.cpp tests/test_openai_provider.cpp temperature and seed are carried as optional settings and sent only when set, because "whatever the provider defaults to" is a real configuration and a model that rejects the parameter must still be reachable — the current reasoning models reject both, so a settings type that always sent a number could not talk to them. An unconfigured install therefore behaves exactly as it did before these existed. --temperature / --seed on the evaluation harness, and the model block of every run record states what was actually used, so a run's repeatability is a fact in the record rather than something the reader has to remember about the command line.
AF-AI-029 Prompt caching for SCCG reviews supported src/review/sccg/sccg_review.cpp, src/ai/openai_provider.cpp, src/app/controllers/ai_review_controller.cpp, src/eval/sccg_review_eval.cpp tests/test_sccg_review_preparation.cpp, tests/test_openai_provider.cpp, tests/test_ai_review_controller.cpp A review request is built as three segments, most shared first: what every review sends (instructions and the response contract, ~2.5k tokens), what every review of one profile and pass sends (the pass sentence, the profile and its rules, ~6-7k), and the element's own data (~1.6k). The text is unchanged -- only the order, with the response contract moved ahead of the element data -- and a test holds that nothing of an element reaches the first two segments. The application places explicit cache breakpoints after the first two and none after the element data, in explicit mode, so it never pays the 1.25x cache-write price for data a review of one element will not read back; prompt_cache_key names SCCG version, profile and pass so requests that share rules reach the same cache. Measured on gpt-5.6-sol (2026-09-11): a first claim reviewed wrote ~9-10k tokens per pass to the cache, and a different claim straight after read ~9k of its ~10.5k input tokens per pass from it, at 0.1x the input price. Requests are no slower: under the same provider load the cached requests were as fast or faster than uncached ones. What caching cannot reduce is output: a claim-review pass produces ~2k output tokens, about 70% of them hidden reasoning, and output is now most of the bill. The evaluation harness also caches each element's data when it reviews an element more than once (--runs > 1), since every later run repeats it, and --no-prompt-cache sends the old single string for comparison. Correction: that single string, and a prompt edited in the debug panel, were described as uncached but were not: with no breakpoints OpenAI places an implicit one at the end of the message. Both now opt out explicitly -- explicit mode with no breakpoints, which OpenAI documents as using no cache and writing none. A billed response whose output is unusable keeps its usage, and a usage block without both token counts is no longer reported as measured usage.

PLAT — Platform and application

ID Capability Status Evidence Tests Notes
AF-PLAT-001 Project model, load and save supported src/core/project_service.cpp, src/core/project_file_io.cpp, src/core/app_state.cpp tests/test_project_service.cpp, tests/test_project_model.cpp, tests/test_app_state.cpp, tests/test_project_semantic_hashes.cpp Creating a project initializes its audit store, as opening one already did. Without it a new project had no manifest and no baseline until its second session: the command bus could not open, every edit took the unaudited path, and accepting a draft reported the SACM file as unwritten and froze the draft mid-promotion. The create dialog reports a name already taken in the parent folder instead of refusing silently. The manifest's semantic hashes are computed over the elements the argument actually contains: semanticHash, elementIndexHash and relationshipGraphHash in af.proj are read through the SACM library (the legacy tag-dialect parser only as a fallback), so a claim's text moves the semantic hash alone, a relationship moves the graph hash, and a reformat moves only the raw hash. Reported from a saved example project: every XMI-dialect argument carried three identical hashes — the SHA-256 of the empty string — because the hash read the file through the legacy parser, which sees nothing in <argumentElement xsi:type=...>, while the load report said the hashes were recalculated. Only the raw hash was ever consulted for the "modified outside Assurance Forge" warning, so nothing decided on the empty digests; they were evidence that meant nothing. An acknowledged external change is reported once (#402): the load report lists each tracked file whose bytes no longer match the hash af.proj records, and pressing OK records that change — file, recorded hash, observed hash — in .af/acknowledged-external-changes.json, so the next open does not warn about it again. Every acknowledged pair is kept, so a file that returns to a change already acknowledged does not warn again, and a file in any other shape (another format or version, or an entry missing a field) acknowledges nothing. An acknowledged change never hides a missing file in the load report. af.proj is not rewritten, because its recorded hash is the evidence that the file was edited outside the tool: the file still shows as modified outside Assurance Forge, and a further change to it is reported again. Before this the warning repeated on every open until something saved the project, which taught people to dismiss it unread. The record is in the unversioned .af/ directory, so it is per checkout: a colleague opening the same project still sees the warning.
AF-PLAT-002 Project explorer prototype src/ui/panels/project_explorer_panel.cpp tests/test_project_controller.cpp Prototype 2 per docs/roadmap/public.md; a redesign is expected.
AF-PLAT-003 Recent projects supported src/core/project_manifest.cpp tests/test_recent_projects.cpp
AF-PLAT-004 Multi-language UI (English, Japanese) supported src/ui/i18n/localization.cpp, src/ui/i18n/mo_catalog.cpp, src/app/app_runtime_project.cpp, src/app/app_runtime_undo.cpp tests/test_localization.cpp, tests/test_ai_error_text.cpp Catalog consistency is gated by the i18n_catalog_check CTest. The status bar was the one surface that bypassed the catalog entirely — 65 message sites in src/app, one of them localized, so a Japanese user saw a translated UI and an English status line. Those 65 were converted (#252), and the conversion did not hold: by 2026-09-26 about 220 status messages in src/app were raw English again. All of them now go through ui::i18n, and the status_message_i18n_check gate fails on a raw literal passed to SetStatus or StatusMessageEvent in src/app, so it cannot drift back unseen. The gate follows literals only: text a lower layer composes and app passes on — a review suggestion refusal, a core connection-rule refusal, an SCCG preparation error — still reaches the status bar in English. AI provider errors — a rejected key, a rate limit, an account with no credit, a network failure — are translated in the status bar and in Preferences' connection status (#451); a review item recording such a failure is saved in the project file and stays English. Bounded: the 27 file- and project-level messages set in core::AppState are still English. The layer rule keeps ui/i18n out of core, so converting them needs the msgid-plus-arguments mechanism rather than a call, and they are tracked on the same issue. Status text also bakes its language when set, so a message already on screen survives a language switch unchanged — a recorded decision, not an oversight; see the comment on AppRuntime::SetStatus.
AF-PLAT-005 Dark and light themes prototype src/ui/theme.cpp, src/ui/fonts.cpp, src/ui/widgets/text_ellipsis.cpp tests/test_theme.cpp, tests/test_text_ellipsis.cpp Prototype 2; the styling model is expected to evolve. Beyond colour the model now carries a four-role typographic scale (ui::fonts) and shared label truncation (ui::widgets::Ellipsize), which panels use so a narrow panel ellipsizes with a tooltip instead of cutting text mid-glyph. Diagnostic surfaces — the menu-bar frame/cull counters, the AI Debug tab and the performance overlay — are off by default behind View ▸ Developer.
AF-PLAT-006 Command bus and undo-aware mutation supported src/core/commands, src/app/app_runtime.cpp tests/test_command_bus.cpp, tests/test_app_events.cpp
AF-PLAT-007 Layered architecture enforced at build time supported cmake/check_layer_gates.cmake cmake/check_layer_gates.cmake ADR 0002. The gate is its own evidence: a configure-time FATAL_ERROR that runs on every build, not a lint.
AF-PLAT-008 Crash-resilient project recovery supported src/core/audit/audit_recovery.cpp tests/test_audit_recovery.cpp
AF-PLAT-009 GSN canvas navigation, selection and hit testing supported src/ui/gsn/gsn_canvas.cpp, src/ui/gsn/gsn_hit_tester.cpp tests/test_gsn_hit_tester.cpp, tests/test_ui_state.cpp Product interaction capability, not a normative Core GSN construct. The picking geometry is pinned in test_gsn_hit_tester.cpp: node-rect containment under pan and zoom, the edge key that tells two drawn instances of one relationship apart, the viewport intersection, and the selected-edge test. PickRelationshipEdge itself reads ImGui hover and mouse state, so its entry point still needs a windowed harness; test_ui_state.cpp covers the transient canvas state around it.
AF-PLAT-010 Package details and navigation supported src/ui/panels/package_details_panel.cpp, src/ui/panels/project_explorer_panel.cpp tests/test_argument_package_projection.cpp UI for SACM packages; does not imply support for GSN Module notation.
AF-PLAT-012 Toolbar for primary actions supported src/ui/panels/toolbar_panel.cpp, src/app/areas/toolbar_area.cpp tests/test_toolbar.cpp Open, save, undo, new SACM file, fit-to-view, export SVG, preferences. Surfaces existing commands only — every button routes to the same callback as its menu item, so the two cannot diverge; it adds no capability of its own. Buttons disable against real state (export needs a loaded case, fit needs the GSN canvas showing) because a control that looks available and does nothing misrepresents what the tool will do. Fit reuses the renderer's existing focus/fit-all request.
AF-PLAT-013 Create a project from an existing SACM file, or import one into the open project supported src/core/project_service.cpp, src/core/app_state.cpp, src/app/app_runtime_project.cpp, src/app/areas/modal_host.cpp, src/ui/panels/welcome_modal.cpp tests/test_project_service.cpp, tests/test_app_state.cpp The welcome screen's Create Project from Existing SACM (also File → Create Project from Existing SACM...) picks a .sacm/.xml file and a parent folder, then creates a project whose first argument is a copy of that file under arguments/, named after the source. File → Import SACM File... copies a file into the open project as another tracked argument and opens it. Both are copies -- the bytes land unchanged and the source is never touched, because the SACM file is the argument and an import that reserialized it would be a silent edit. The source must load through the SACM library first, with the library's own diagnostic in the refusal, so a project never tracks an argument it cannot open; the create checks this before scaffolding, so a refused file leaves no empty project folder behind. A name the project already tracks is refused, not overwritten. The imported argument gets its own audit snapshot 0, so it is auditable from the first edit. Not yet: import does not carry a source file's af.proj siblings (registers, review state) -- it is one argument file, not a project merge -- and the file is not validated against SCCG or GSN well-formedness at import; those run once it is open.
AF-PLAT-015 Example project and guides on the welcome screen supported src/app/example_project.cpp, src/app/areas/modal_host.cpp, src/ui/panels/welcome_modal.cpp, cmake/copy_example_project.cmake, cmake/packaging.cmake tests/test_example_project.cpp The welcome screen's first action opens the kitchen-blender example from assurance-forge-examples, shipped beside the executable in every build and package. The shipped copy is never opened in place: the first click copies it to Assurance Forge Examples in the user's Documents folder (the shell's folder, so a OneDrive redirection is followed) and later clicks reopen that copy, so a user's edits are never overwritten. The copy leaves out any .af/ audit history, which would otherwise open the example warning that its history does not match its SACM. A build without the examples submodule does not offer the action. The three Guides cards open user-guide pages in the browser; on Linux and macOS through xdg-open / open, which is not exercised by a test. They replace a template action and three walkthroughs that only said "not yet implemented".
AF-PLAT-011 Status bar: save state, active document, problem counts supported src/ui/panels/status_bar_panel.cpp, src/app/areas/status_bar_area.cpp tests/test_status_bar.cpp Saved and unsaved render as distinct labels plus a dot, never as the absence of one — an ambiguous save indicator is a trust problem when the artifact is a safety argument. Also the sink for core::AppState::status_message: roughly twenty call sites set it and, before this, only the SACM viewer panel rendered it, so most feedback (load warnings, save failures) was produced and discarded. Save state is reported from AppState::has_unsaved_changes, which the audited dispatch clears when CommandResult::sacm_written confirms the command bus wrote the file — before this, an autosaved edit stayed marked unsaved forever because the ~30 DocumentDirtyEvent emitters run after the dispatch and cannot know it was persisted. Only command-bus edits autosave; paths that bypass the bus mutate in memory and are genuinely unsaved until an explicit save, so the indicator is not a claim that every edit is persisted. No last-saved timestamp yet.
AF-PLAT-014 Windows installer and portable zip prototype cmake/packaging.cmake, cmake/packaging_project_config.cmake, packaging/windows/installer.iss, packaging/windows/installer_code.pas, .github/workflows/release.yml tools/release/check_windows_package.py A per-user Inno Setup installer (no administrator rights; an all-users install is offered to someone who can elevate) and a portable zip, both built by CPack from one set of install() rules. Each holds the GUI, assurance-forge-mcp, the SCCG catalogue, the sample files and the Visual C++ runtime, which the zip before it did not ship -- a machine without the VC++ Redistributable could not start the released exe, and the MCP server was not in the release at all. The installer runs in English or Japanese, follows Windows' light or dark mode on the application's own colours, shows a screenshot and what the tool does on a first install, lists the built-in features (each a supported row here) ticked and locked, explains the built-in AI review (own API key) and the MCP server (own assistant) side by side on one page, makes the sample cases and the MCP server optional, offers to register the MCP server with Claude Code or Codex when either is on PATH (never touching an entry it did not create, and removing only its own on uninstall; the in-app consent gate still applies), upgrades an earlier install in place (skipping the folder page), offers the user guide after a first install and the release notes after an upgrade, and on uninstall asks whether to keep the user's settings and saved API key, defaulting to keep. The wizard's pages have not yet been reviewed on screen at every DPI and in both modes. The release workflow checks both packages before publishing: required files present, every imported DLL either part of Windows or shipped, and the MCP server starting from the package; the installer is installed silently, checked and uninstalled. Prototype: the packages are not code-signed, so SmartScreen warns on first run, and the file association for SACM files and an MSI for managed deployment are not built. Windows only; Linux and macOS releases remain hand-staged archives.