Skip to content

feat(MeshActionRegistry): Apple-parity mesh-wide action discovery, without ambient trust - #216

Merged
mdheller merged 1 commit into
mainfrom
rescue/mesh-action-registry
Jul 30, 2026
Merged

feat(MeshActionRegistry): Apple-parity mesh-wide action discovery, without ambient trust#216
mdheller merged 1 commit into
mainfrom
rescue/mesh-action-registry

Conversation

@mdheller

Copy link
Copy Markdown
Contributor

Apple's AppIntents surface is system-wide by default: any app can register intents any other app can invoke, all queryable via one AppIntentSchemas.sqlite. This spec gives the estate the equivalent SHAPE — without the ambient trust.

What lands

  • schemas/MeshActionRegistry.json — signed, digest-pinned registry of participants
  • examples/mesh-action-registry.example.json — 2-participant reference
  • tools/validate_mesh_action_registry.py — validator, 3 positive + 12 negative
  • Makefile wired via validate-mesh-action-registry
  • .github/workflows/mesh-action-registry.yml — dedicated CI

Discovery, not shared execution

The mesh registry is a catalogue of pointers, not an executor:

  1. Each participant is a repo with its own signed .sourceos/manifest.json (SourceOSRepoManifest, hardened this session to reject empty policyClasses).
  2. Each participant exposes an action catalogue conforming to Noetica's ActionCatalogEntry schema (PR #572), typed with the ValueType algebra and carrying consentRequired + capabilityRef.
  3. Each participant catalogRef is pinned by catalogDigest — a swap of the remote catalogue is detectable, not silently trusted.
  4. The registry itself is signed and carries registryDigest for tampering detection.
  5. verbOntologyRef is optional per-participant (see Noetica's verb-ontology.json, PR #573); absent means the participant doesn't classify by verb, a weaker claim.

Execution stays governed by the origin app's own capability-ledger and consent gates. A consumer walking the mesh sees what actions exist and their type shape, but each invocation still passes through the origin's local gates.

Where this beats Apple

Apple AppIntents MeshActionRegistry
System-wide surface ✓ (device-scoped) ✓ (mesh-scoped)
Cross-app discovery
Ambient trust assumed — any signed app can invoke any intent denied — each participant is signed and digest-pinned; consumer verifies before trusting
Consent gate absent at intent level consentRequired per-action from ActionCatalogEntry
Governance visibility none — intents don't declare policy class policyClasses on referenced manifest
Verb classification 4 primitives, 156/203 unclassified rich ontology anchored in KKO (PR #573)

Verification

15/15 validator cases pass:

  • 3 positive: full example, unsigned variant, single-participant
  • 12 negative: empty participants, bare-string digests, missing manifestRef, missing catalogDigest, missing signature.algorithm, unknown algorithm, wrong repo shape, non-date-time generatedAt, wrong type discriminator, wrong URN namespace, undeclared field on a participant

Date-time is enforced with a pattern because jsonschema treats format as annotation-only by default — same lesson as the semantic-proof fix earlier this session.

Follow-up (not this PR)

  • Runtime walker that pulls each participant's catalogue and verifies digests.
  • Consumer example that walks the mesh and produces a union view of typed actions.

…-wide actions

Apple's AppIntents surface is system-wide by default: any app can register
intents any other app can invoke, all queryable via one AppIntentSchemas.sqlite.
This spec gives the estate the equivalent SHAPE without the ambient trust:

  - Each participant's catalogue is referenced by a signed manifest
    (SourceOSRepoManifest, hardened this session to reject empty policyClasses).
  - Each participant's action catalogue conforms to Noetica's
    ActionCatalogEntry (PR #572) — typed with the ValueType algebra we
    shipped, carrying consentRequired and capabilityRef.
  - The mesh registry ITSELF is signed and carries a registryDigest so
    consumers detect a tampered snapshot.
  - Each participant's catalog is pinned by catalogDigest so a swap of the
    remote catalogue file is visible as a digest mismatch, not silent trust.
  - Verb ontology reference is optional per-participant (see Noetica's
    verb-ontology.json, PR #573); absent means the participant does not
    classify by verb, a weaker claim Apple ships.

Execution stays governed by the origin app's own capability-ledger and
consent gates — this is a discovery pattern, not a shared executor.

15/15 validator cases pass:
  3 positive:  full example, unsigned variant, single-participant
  12 negative: empty participants, bare-string digests, missing manifestRef,
               missing catalogDigest, missing signature.algorithm, unknown
               algorithm, wrong repo shape, non-date-time generatedAt, wrong
               type discriminator, wrong URN namespace, undeclared field

Date-time is enforced with a pattern because jsonschema treats format as
annotation-only by default — same lesson as the semantic-proof fix earlier
this session.
@mdheller
mdheller force-pushed the rescue/mesh-action-registry branch from cc547fd to 66d5ebb Compare July 30, 2026 01:32
mdheller added a commit that referenced this pull request Jul 30, 2026
…wide contract

The estate's governance thesis is that a dispatch is true only when both a Law
factor and an Evidence factor are established. It was stated in one app's
docstring and enforced nowhere: Noetica's dispatch ledger declared it while all
five call sites passed the literal 'POS', and 34 of 53 real entries recorded
`grounded: false` alongside `verdict: 'POS'` in the same JSON object.

An implementation can always drift back into that. A schema cannot. Put the
algebra in the spec so any app on the mesh — Noetica, prophet-workspace,
prophet-mesh agents, agora, ops-fabric — emits the same shape and a validator in
any language enforces it without trusting the emitter.

Give `×` its precise meaning: the MEET in the chain NEG < ZERO < POS (strong-
Kleene conjunction, i.e. min). A three-element chain is a category, so that meet
IS the categorical product — the equation is an identity, not a slogan. It is
NOT sign multiplication in {-1,0,+1}, which gives NEG × NEG = POS: a refused
gate and a refuted outcome certifying as true.

Two invariants, both enforced by shape:

  1. verdict == law.factor × evidence.factor. The 9 branches are GENERATED from
     min() rather than hand-typed, so the schema cannot disagree with the algebra
     by transcription error.
  2. evidenceTier T1 is unavailable when either factor is 'declared'. T1 means
     instrumented; claiming it for a value no instrument produced is the same
     defect one level up. Under-claiming is always permitted.

Ships conformance/lawful-verdict-vectors.json — the cross-language anti-drift
device. Two implementations that each pass their own unit tests can still
disagree with each other; only a shared vector set makes that detectable. Both
Noetica (TypeScript) and prophet-platform libs/python/lawful-verdict test against
this file, and the sealed example receipt is verified byte-identically by both
canonicalisers.

The validator asserts by REJECTION, 64 checks: all 9 cells accepted, all 18 wrong
verdicts rejected, NEG × NEG = POS rejected by name, T1 rejected whenever a
factor is declared, the example's seal recomputed, the vectors checked against
min(), and 15 shape controls. Proven able to fail: admitting NEG × NEG = POS in
the schema makes it exit 1.

Two defects found and fixed while building it. The regex classes were
over-escaped, so schemaVersion '0.1.0' failed its own pattern. Worse, the
validator printed "all 9 cells accepted" as an unconditional print() and duly
printed it while 9 of 9 cells were failing — a gate narrating success
independently of its own result, the exact defect this contract exists to
prevent, reproduced inside the checker for it. Every section line now derives
OK/FAIL from the failure list.

Stacked on rescue/mesh-action-registry (#216) because the Makefile target sits
beside validate-mesh-action-registry; retargets cleanly to main once that lands.
@mdheller
mdheller merged commit ded0254 into main Jul 30, 2026
8 checks passed
@mdheller
mdheller deleted the rescue/mesh-action-registry branch July 30, 2026 01:35
mdheller added a commit that referenced this pull request Jul 30, 2026
…wide contract

The estate's governance thesis is that a dispatch is true only when both a Law
factor and an Evidence factor are established. It was stated in one app's
docstring and enforced nowhere: Noetica's dispatch ledger declared it while all
five call sites passed the literal 'POS', and 34 of 53 real entries recorded
`grounded: false` alongside `verdict: 'POS'` in the same JSON object.

An implementation can always drift back into that. A schema cannot. Put the
algebra in the spec so any app on the mesh — Noetica, prophet-workspace,
prophet-mesh agents, agora, ops-fabric — emits the same shape and a validator in
any language enforces it without trusting the emitter.

Give `×` its precise meaning: the MEET in the chain NEG < ZERO < POS (strong-
Kleene conjunction, i.e. min). A three-element chain is a category, so that meet
IS the categorical product — the equation is an identity, not a slogan. It is
NOT sign multiplication in {-1,0,+1}, which gives NEG × NEG = POS: a refused
gate and a refuted outcome certifying as true.

Two invariants, both enforced by shape:

  1. verdict == law.factor × evidence.factor. The 9 branches are GENERATED from
     min() rather than hand-typed, so the schema cannot disagree with the algebra
     by transcription error.
  2. evidenceTier T1 is unavailable when either factor is 'declared'. T1 means
     instrumented; claiming it for a value no instrument produced is the same
     defect one level up. Under-claiming is always permitted.

Ships conformance/lawful-verdict-vectors.json — the cross-language anti-drift
device. Two implementations that each pass their own unit tests can still
disagree with each other; only a shared vector set makes that detectable. Both
Noetica (TypeScript) and prophet-platform libs/python/lawful-verdict test against
this file, and the sealed example receipt is verified byte-identically by both
canonicalisers.

The validator asserts by REJECTION, 64 checks: all 9 cells accepted, all 18 wrong
verdicts rejected, NEG × NEG = POS rejected by name, T1 rejected whenever a
factor is declared, the example's seal recomputed, the vectors checked against
min(), and 15 shape controls. Proven able to fail: admitting NEG × NEG = POS in
the schema makes it exit 1.

Two defects found and fixed while building it. The regex classes were
over-escaped, so schemaVersion '0.1.0' failed its own pattern. Worse, the
validator printed "all 9 cells accepted" as an unconditional print() and duly
printed it while 9 of 9 cells were failing — a gate narrating success
independently of its own result, the exact defect this contract exists to
prevent, reproduced inside the checker for it. Every section line now derives
OK/FAIL from the failure list.

Stacked on rescue/mesh-action-registry (#216) because the Makefile target sits
beside validate-mesh-action-registry; retargets cleanly to main once that lands.
mdheller added a commit that referenced this pull request Jul 30, 2026
…wide contract (#218)

* schemas: LawfulDispatchReceipt — Truth = Law × Evidence as an estate-wide contract

The estate's governance thesis is that a dispatch is true only when both a Law
factor and an Evidence factor are established. It was stated in one app's
docstring and enforced nowhere: Noetica's dispatch ledger declared it while all
five call sites passed the literal 'POS', and 34 of 53 real entries recorded
`grounded: false` alongside `verdict: 'POS'` in the same JSON object.

An implementation can always drift back into that. A schema cannot. Put the
algebra in the spec so any app on the mesh — Noetica, prophet-workspace,
prophet-mesh agents, agora, ops-fabric — emits the same shape and a validator in
any language enforces it without trusting the emitter.

Give `×` its precise meaning: the MEET in the chain NEG < ZERO < POS (strong-
Kleene conjunction, i.e. min). A three-element chain is a category, so that meet
IS the categorical product — the equation is an identity, not a slogan. It is
NOT sign multiplication in {-1,0,+1}, which gives NEG × NEG = POS: a refused
gate and a refuted outcome certifying as true.

Two invariants, both enforced by shape:

  1. verdict == law.factor × evidence.factor. The 9 branches are GENERATED from
     min() rather than hand-typed, so the schema cannot disagree with the algebra
     by transcription error.
  2. evidenceTier T1 is unavailable when either factor is 'declared'. T1 means
     instrumented; claiming it for a value no instrument produced is the same
     defect one level up. Under-claiming is always permitted.

Ships conformance/lawful-verdict-vectors.json — the cross-language anti-drift
device. Two implementations that each pass their own unit tests can still
disagree with each other; only a shared vector set makes that detectable. Both
Noetica (TypeScript) and prophet-platform libs/python/lawful-verdict test against
this file, and the sealed example receipt is verified byte-identically by both
canonicalisers.

The validator asserts by REJECTION, 64 checks: all 9 cells accepted, all 18 wrong
verdicts rejected, NEG × NEG = POS rejected by name, T1 rejected whenever a
factor is declared, the example's seal recomputed, the vectors checked against
min(), and 15 shape controls. Proven able to fail: admitting NEG × NEG = POS in
the schema makes it exit 1.

Two defects found and fixed while building it. The regex classes were
over-escaped, so schemaVersion '0.1.0' failed its own pattern. Worse, the
validator printed "all 9 cells accepted" as an unconditional print() and duly
printed it while 9 of 9 cells were failing — a gate narrating success
independently of its own result, the exact defect this contract exists to
prevent, reproduced inside the checker for it. Every section line now derives
OK/FAIL from the failure list.

Stacked on rescue/mesh-action-registry (#216) because the Makefile target sits
beside validate-mesh-action-registry; retargets cleanly to main once that lands.

* schemas: bind each FACTOR to its record (invariants 3 & 4); pin the seal functions

Adversarial review of the first revision found a hole that mattered more than
either invariant it shipped with. The schema enforced verdict = law × evidence but
NOTHING bound the factors to their own evidence, so all six of these were ACCEPTED:

  law.factor: POS      alongside  barCleared: false      (the gate REFUSED)
  law.factor: POS      alongside  residual: [...]        (undischarged)
  evidence.factor: POS alongside  refuted: true
  evidence.factor: POS with       no digests at all
  evidence.factor: POS alongside  grounded: false
  evidence.factor: POS with       grounded absent

The product then faithfully multiplied the lie. Enforcing an invariant one level
too high is indistinguishable from not enforcing it.

INVARIANT 3: law.factor is a function of (barCleared, residual).
INVARIANT 4: evidence.factor is a function of (digests, grounded, refuted).

Both enforced BOTH ways, so a factor can be neither over- nor under-claimed: POS
requires both well-formed digests AND grounded AND not refuted; NEG requires an
actual refutation; everything else is ZERO. Verified exhaustively — 12 law and 24
evidence combinations, accepting only the derivable factor — plus the six attacks
asserted by name so a future edit that reopens one fails loudly instead of quietly
dropping a count.

Also pins the SEAL FUNCTIONS, after review on prophet-platform#1065 found two
cross-language divergences that every receipt-shaped test had missed:

  canonicalJson  Python's default ensure_ascii=True escapes "café" to "café"
                 while JavaScript emits it raw, so every seal over non-ASCII
                 content diverged. The cross-language seal test passed anyway
                 because the example receipt is pure ASCII.
  contentHash    Python hashed raw bytes; TypeScript hashes canonicalJson(s), the
                 QUOTED JSON encoding. They disagreed on EVERY input. Nothing
                 caught it because the schema only checks that a digest is
                 well-formed, and a wrong digest is still well-formed.

New `canonicalJson` (6 cases: sorted keys, recursive, accented text, CJK,
astral-plane emoji, bare string) and `contentHash` (5 cases incl. empty string,
non-ASCII, embedded newlines and JSON metacharacters) vector sections make the
functions themselves conformance-checked rather than merely their output shape.
The validator asserts the vectors against its own canonicaliser AND requires at
least one non-ASCII case, so the trap cannot be silently removed. TypeScript is
canonical by incumbency: it sealed the production ledger.

Two fixes to the validator itself. with_factors now produces DERIVABLE factor
bodies — without that, invariants 3/4 reject every product case and all 27 pass
vacuously, the same trap as comparing two empty result sets and calling them
identical. And its own canonical_json needed ensure_ascii=False, or it could not
check the very cases it ships.

64 checks → 118. 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.

1 participant