Skip to content

schemas: ArchitecturalBuildingBlock — role-named components, decoupled from vendors - #223

Merged
mdheller merged 1 commit into
mainfrom
feat/architectural-building-block
Jul 30, 2026
Merged

schemas: ArchitecturalBuildingBlock — role-named components, decoupled from vendors#223
mdheller merged 1 commit into
mainfrom
feat/architectural-building-block

Conversation

@mdheller

Copy link
Copy Markdown
Contributor

First move in the Michael-agent capability-integration work. Adds the ABB abstraction the estate has been missing.

Why

Zurich E-RDA2 uses the same pattern — ABB.03 = DATABASE has slots that DB2, ADLS, BLOB, XYZ or CaaS can fill. Without an ABB layer, a service is inseparable from its implementation and 'we use Postgres' becomes a load-bearing invariant across ~74 services.

An ABB names a function (DATABASE, IMPLEMENTATION CONTROLLER), not a technology (PostgreSQL, MSTR). Implementations declare implementsAbb: ['ABB.03'] in their MeshActionRegistry participant and become swappable slot fillers.

Invariants (both enforced by REJECTION)

  1. abbId is ABB.<two digits> — ids are stable; renumbering is a breaking change to every manifest that references them.
  2. protocol.reads and protocol.writes are declared explicitly (may be empty). A write-only ABB writes reads: [] on purpose — 'forgot' cannot masquerade as 'no reads'.

Plus a validator-side check that roles name functions not vendors: postgres, AWS, MSTR, kubernetes are all caught by a deny list.

Consumers (in flight)

  • prophet-mesh Michael Agent's route_by_abb capability
  • Noetica intent-grid's ABB-aware cell derivation
  • prophet-platform libs/python/solution-ranker (walks MAR for candidates)
  • prophet-platform libs/python/access-prewalk (graded consent per candidate)

Verification

  • 22 validator checks, all pass
  • make validate green (17 schema families)
  • Proven able to fail: loosening abbId pattern to ^.+$ exits 1; restored, exits 0

…d from vendors

Adds the ABB abstraction the estate has been missing. Zurich E-RDA2 uses the same
pattern — ABB.03 = DATABASE has slots that DB2, ADLS, BLOB, XYZ or CaaS can fill — because
it lets a vendor swap be a one-manifest edit rather than a call-site rewrite. Without it,
'we use Postgres' becomes a load-bearing invariant across ~74 services.

An ABB names a FUNCTION (`DATABASE`, `IMPLEMENTATION CONTROLLER`), not a technology
(`PostgreSQL`, `MSTR`). Implementations declare `implementsAbb: ['ABB.03']` in their
MeshActionRegistry participant entry and immediately become swappable slot fillers.
Consumers derive the shape by walking MAR and filtering — never authored alongside the
consuming code, following the same discipline that keeps intent-grid derived from the canon
rather than shadowing it.

Two invariants enforced by shape (asserted by REJECTION, a validator never observed
refusing is indistinguishable from no validator):

  1. abbId is `ABB.<two digits>`. Ids are stable — renumbering is a breaking change to every
     manifest, so `supersedes: [ABB.03]` retires with a forwarding pointer rather than
     renumbering in place.
  2. protocol.reads and protocol.writes are declared explicitly (may be empty).
     A missing verb list is the failure — a write-only ABB writes `reads: []` on purpose,
     so 'I forgot' cannot masquerade as 'no reads'.

Plus a validator-side check that role names are FUNCTIONS not VENDORS: 'postgres',
'PostgreSQL', 'mysql', 'AWS', 'MSTR', 'kubernetes' as roles are all caught by a deny list.
Not exhaustive by design — the goal is to catch obvious slips, not enumerate every product.
Determined authors can still name a role poorly; review catches what this misses.

Consumers (in flight): prophet-mesh Michael Agent's `route_by_abb` capability,
Noetica intent-grid's ABB-aware cell derivation, prophet-platform libs/python/solution-ranker
(walks MAR for candidates), libs/python/access-prewalk (graded consent per candidate).

22 checks. `make validate` green. Proven able to fail: loosening the abbId pattern to `^.+$`
exits 1; restored, exits 0.
Copilot AI review requested due to automatic review settings July 30, 2026 07:02
@mdheller
mdheller merged commit 26e2b60 into main Jul 30, 2026
9 checks passed
mdheller added a commit that referenced this pull request Jul 30, 2026
… their ABB slots (#224)

Second move in the Michael-agent capability-integration work. #223 (ArchitecturalBuildingBlock)
gave us role-named components; this lets a participant CLAIM the roles it fills.

    participants[*].implementsAbb: ["ABB.03", "ABB.07"]

A consumer walking the registry for an ABB (Michael's `route_by_abb`, or solution-ranker's
candidate shortlist) filters on this. Absent/empty means the participant is not claimed as a
slot filler — the consumer won't consider it.

This field only establishes the CLAIM, not its truth. Verification is the consumer's job:
walk the ABB's protocol + requiredCapabilities, confirm the participant delivers each. That
split is deliberate — a validator here can reject unknown `ABB.NN` patterns but cannot know
whether a participant actually satisfies the protocol without running the caller. Making
that split explicit stops any misplaced trust in "the registry validated this so it must work."

Enforced by 7 new validator cases (unknown pattern, bare number, three-digit, wrong
namespace, duplicates, empty ⇒ absent-shaped, present ⇒ well-shaped). Existing 15 shape
cases still pass. `make validate` green.

The example receipt now shows two participants claiming ABB.01/02 — so a consumer can
already dry-run the walk and get a non-trivial shortlist.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new ArchitecturalBuildingBlock (ABB) normative schema + example and wires in validation/CI so ABB IDs are stable (ABB.NN) and protocol.reads/protocol.writes are explicitly declared, enabling role-based (“function, not vendor”) component substitution across the estate.

Changes:

  • Introduces ArchitecturalBuildingBlock JSON Schema with closed shape, stable abbId pattern, role naming, and explicit protocol.reads/writes.
  • Adds a shipped ABB example (ABB.03 / DATABASE) plus a Python validator with negative controls.
  • Wires validation into make validate and a dedicated GitHub Actions workflow for ABB-related files.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tools/validate_architectural_building_block.py Adds a schema+example validator with negative controls (but currently does not actually enforce vendor-role rejection as described).
schemas/ArchitecturalBuildingBlock.json Defines the ABB contract (closed object, stable ID pattern, protocol verb lists, optional conformance pointer).
examples/architectural-building-block.example.json Adds a canonical ABB example (ABB.03 DATABASE) with protocol verbs and required capabilities (currently references a missing conformance file).
Makefile Adds validate-architectural-building-block target and wires it into validate.
.github/workflows/architectural-building-block.yml Adds CI gate that runs the ABB validator on schema/example/validator/workflow changes.
Comments suppressed due to low confidence (1)

tools/validate_architectural_building_block.py:110

  • The INVARIANT 1 block only checks whether the regex matches a few sample strings; it never asserts that vendor-named roles are actually rejected by the validator. After enforcing vendor-role rejection in case(), this section should use case(..., should_pass=False) so CI will fail if the deny list regresses.
    vendor_examples = ["postgres", "PostgreSQL", "mysql", "AWS", "MSTR", "kubernetes"]
    vendor_failures = 0
    for v in vendor_examples:
        e = copy.deepcopy(example)
        e["role"] = v

Comment on lines +61 to +69
def case(label: str, instance: Any, should_pass: bool) -> None:
nonlocal checks
checks += 1
errs = sorted(validator.iter_errors(instance), key=lambda e: list(e.path))
accepted = not errs
if accepted != should_pass:
want = "accepted" if should_pass else "rejected"
got = "accepted" if accepted else f"rejected ({errs[0].message[:100]})"
failures.append(f"{label}: expected {want}, was {got}")
Comment on lines +11 to +17
"requiredCapabilities": [
"at-rest-encryption",
"row-level-auth",
"audit-chain-append",
"point-in-time-recovery"
],
"conformanceTest": "conformance/abb-database-vectors.json"
"abbId": {
"type": "string",
"pattern": "^ABB\\.[0-9]{2}$",
"description": "Two-digit ABB identifier (ABB.01, ABB.02, ..., ABB.99). Namespaced by role, not by vendor — vendor is a property of the implementation, not the block. ABBs are numbered stably: renumbering an ABB is a breaking change to every manifest that references it, so ids are assigned once and never reused."
mdheller added a commit that referenced this pull request Jul 30, 2026
)

#223 shipped three instances of the defect class it was written to close.

1. The vendor-role control never looked at a document. It built one
   (`e = deepcopy(example); e["role"] = v`) and then never used it, matching
   its regex against six hardcoded strings instead. With `role: "postgres"`
   in the shipped example the validator exited 0 AND printed
   "OK 6 vendor-named roles caught by the deny list". A control that reports
   catching what it has not looked at is worse than an absent one, because it
   produces evidence.

2. `role` was `{type: string, minLength: 1}`. A deny list cannot prevent the
   failure that actually compounds — the same role spelled several ways.
   macOS ships 627 sandbox containers whose extension-point names include
   `extension` (45x), `Extension` (2x), `DiagnosticExtension` (8x),
   `diagnosticextension` (5x) and `diagnostic` (3x): fifteen years of a
   freeform role field with no grammar, after which the name can no longer
   group anything and a side-channel record has to carry the contract.

3. `conformanceTest` pointed at `conformance/abb-database-vectors.json`,
   which does not exist. `format: uri-reference` is a syntax check and
   resolves nothing, so the dangling pointer validated clean.

Changes:

- `role` is a closed enum over the four roles actually in use (prophet-mesh
  specs/abb-catalog.yaml plus the example). Rejection now happens in the
  schema, which every consumer already validates against, not in a private
  regex. Adding a role is a spec PR — the reviewed gate whose absence
  produced the five spellings above.
- The deny list is deleted. Its cases are now fed to the schema AS DOCUMENTS:
  8 vendor names, 6 case/whitespace variants, 1 invented role, all rejected.
- INVARIANT 1a mutation-tests the enum. Without it the cases above would
  keep passing if `role` reverted to a freeform string while some unrelated
  constraint did the rejecting. Drop the enum, confirm `postgres` is
  accepted; restore, confirm rejected.
- INVARIANT 3 resolves `conformanceTest` against the repo, with a negative
  control proving a dangling pointer is detected. The example's dangling
  pointer is removed rather than backfilled with invented vectors: absence is
  documented as meaningful, a fabricated suite would be a new paper control.

Verified by probe, not by exit code: the exact document that passed before
(`role: postgres`) now fails, as do `role: database` and a dangling
conformanceTest. 34 checks, `make validate` green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants