Skip to content

docs: add ADR for evidence fabric boundaries and contract ownership - #13

Merged
mdheller merged 1 commit into
mainfrom
docs/evidence-fabric-boundaries-v0-1-rebased
Apr 9, 2026
Merged

docs: add ADR for evidence fabric boundaries and contract ownership#13
mdheller merged 1 commit into
mainfrom
docs/evidence-fabric-boundaries-v0-1-rebased

Conversation

@mdheller

@mdheller mdheller commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an ADR that freezes the boundary between the umbrella SourceOS metadata plane and the planned operational evidence fabric.

What this PR adds

  • docs/adr/ADR-evidence-fabric-boundaries-v0-1.md

Why

The current ecosystem now has enough moving pieces that repo and contract ownership needs to be explicit before evidence-plane repos are created and implemented.

This ADR locks the following points:

  • sourceos-spec remains the umbrella metadata-plane and governance contract layer
  • the evidence fabric becomes a distinct operational contract family owned in SocioProphet
  • runtime evidence objects should not reuse umbrella names like RunRecord
  • operational evidence contracts use kind rather than type
  • desktop-facing account and capture surfaces remain with SociOS-Linux

Current upstream posture

This PR was rebased onto the current visible main base exposed by the connector (1dfa4b6067904fc1ebd5c708b3dcd8c379659159).

Follow-on work

  1. create the dedicated evidence-plane repo family in SocioProphet
  2. align evidence-plane provenance/connector mappings upward to sourceos-spec
  3. avoid absorbing operational custody schemas directly into sourceos-spec

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 Architecture Decision Record (ADR) to formalize/“freeze” the boundary between the umbrella SourceOS metadata-plane contracts and a separate operational evidence-fabric contract family (including ownership and naming/discriminator rules).

Changes:

  • Introduces an ADR describing evidence-fabric boundaries, reserved names, and the kind vs type discriminator rule.
  • Documents repo ownership split across SourceOS-Linux, SocioProphet, and SociOS-Linux.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -0,0 +1,123 @@
# ADR: Evidence Fabric Boundaries and Contract Ownership (v0.1)

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

ADR filenames and titles in this repo follow the numbered pattern (e.g., docs/adr/0005-... with a # ADR-0005: ... heading). Consider renaming this to the next available number (currently 0006-...) and updating the H1 to match, so it stays consistent with the existing ADR index/sorting.

Suggested change
# ADR: Evidence Fabric Boundaries and Contract Ownership (v0.1)
# ADR-0006: Evidence Fabric Boundaries and Contract Ownership (v0.1)

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Rejected — the premise does not hold.

The suggestion is to renumber this ADR to match a numbered convention and take "the next available number (currently 0006)". There is no such convention in docs/adr/ to match. Actual state of the directory:

number times used
0001 8
0012 3
0002, 0006, 0009, 0010 2 each

Three different header styles are also in use (# ADR-0001:, # ADR 0001 —, and ## Status sections), including in 0000-template.md's own neighbours. Renaming this one file would not make the collection uniform; it would just add a ninth 000x collision. 0006 in particular is already taken twice — 0006-evidence-fabric-boundaries.md and ADR-0006-polycentric-observer-schema-family.md — so the specific number suggested here is not available.

There is a real problem next to this one that the review did not name: 0006-evidence-fabric-boundaries.md now exists, with the conventional header, covering the same decision as this file, with different content. Two ADRs for one decision is worth resolving; renaming either of them is not, and choosing which is canonical is an editorial call rather than a cleanup, so I am flagging it rather than doing it silently.

Closing this thread as answered, not as done.

Comment on lines +3 to +6
## Status

Proposed.

Copilot AI Apr 9, 2026

Copy link

Choose a reason for hiding this comment

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

The ADR header metadata format here diverges from the project ADR template: other ADRs use a top metadata block like **Status:** ..., **Date:** ... (and sometimes **Deciders:** ...) followed by a --- divider. Updating this ADR to match the template will keep the collection uniform and easier to scan.

Suggested change
## Status
Proposed.
**Status:** Proposed
---

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Rejected — the premise does not hold.

The suggestion is to renumber this ADR to match a numbered convention and take "the next available number (currently 0006)". There is no such convention in docs/adr/ to match. Actual state of the directory:

number times used
0001 8
0012 3
0002, 0006, 0009, 0010 2 each

Three different header styles are also in use (# ADR-0001:, # ADR 0001 —, and ## Status sections), including in 0000-template.md's own neighbours. Renaming this one file would not make the collection uniform; it would just add a ninth 000x collision. 0006 in particular is already taken twice — 0006-evidence-fabric-boundaries.md and ADR-0006-polycentric-observer-schema-family.md — so the specific number suggested here is not available.

There is a real problem next to this one that the review did not name: 0006-evidence-fabric-boundaries.md now exists, with the conventional header, covering the same decision as this file, with different content. Two ADRs for one decision is worth resolving; renaming either of them is not, and choosing which is canonical is an editorial call rather than a cleanup, so I am flagging it rather than doing it silently.

Closing this thread as answered, not as done.

@mdheller
mdheller merged commit b45886d into main Apr 9, 2026
8 checks passed
@mdheller
mdheller deleted the docs/evidence-fabric-boundaries-v0-1-rebased branch April 9, 2026 13:40
mdheller added a commit that referenced this pull request Aug 2, 2026
… governance substrate (task #13) (#250)

Language regulates state, so an approved term must be usable to reason about governance:
CAPTURED (ontology class) + VECTOR-ALIGNED (NP↔VP link in the sovereign 768 space) +
IMPLEMENTED (estate entity/service). Extends the existing GlossaryTerm (optional, backward-
compatible) with partOfSpeech (NP/VP, Tesnière), status, a 3-method alignment block, and
typed relations over a CLOSED RDF/RDFS/SKOS/FOAF predicate set (is-type/has-type, is-a, has-a,
is-member/has-member, skos:broader/narrower/related, foaf:member, has-datatype).

Fail-closed drift-guard (validate_glossary_alignment_examples.py): an approved term missing any
alignment method is a governance hole; NP→entity, VP→action/service; vector links reciprocal;
relation inverse-pairs reciprocate; skos:related symmetric; is-a subsumption ACYCLIC (so a reasoner
can derive dependencies+constraints). Teeth-verified (approve-unaligned, broken reciprocity,
broken symmetry, is-a cycle all fail). Dogfood: the draft example is SP-GATE-004's release_gates.*
new vocab, unaligned. make validate ok; dup-$id 345.

Follow-ups: LSA↔LDA currency detection; agreement test vs the blast-radius graph + neurosymbolic domain.
mdheller added a commit that referenced this pull request Aug 2, 2026
…erned (task #13) (#255)

* feat(vocab): LIVE vocab-currency GovernedLoop — LSA(glossary)↔LDA(corpus) currency (task #13)

Makes the vocabulary-currency loop live and GOVERNED by the merged GovernedLoop contract.
The approved glossary is the fixed set (LSA), the corpus token distribution is the open set
(LDA), and uncovered probability mass is the currency signal (the LSI drive — new fields that
became vocab but aren't connected). Remediation connects the highest-mass uncovered term,
strictly decreasing divergence (monotone-decrease is real). The runner reads bound + tolerance
+ onNonConvergence + admission FROM the contract and obeys them.

Fail-closed teeth (asserted by validate-vocab-currency-loop, wired into make validate):
- convergent corpus reaches currency (ok);
- a domain the vocab can't name within the bound ESCALATES-human, never silently passes,
  never spins past maxIterations;
- a loop with no superconscious admission is REFUSED (loops don't self-authorize).
Emits candidateNewVocab for routing into ontogenesis/prophet-ontology. Dependency-free.

* fix(vocab): rename misleading --emit-candidates to --trace (own review)

The flag gated the per-iteration trace, not the candidate list (candidateNewVocab is always
emitted), so the name misdescribed its effect. Renamed to --trace.

* fix(vocab): remediate Copilot review on #255 — measure guard + null tolerance + conformance-first

- run_loop now REFUSES a contract whose convergence.measure this runner doesn't implement
  (only monotone-decrease/error-below-tolerance; a 'fixpoint' contract is refused rather than
  run with mismatched semantics — don't honor a guarantee you don't provide).
- convergence.tolerance may be null per schema; treat null as 0.0 (must fully cover) instead of
  crashing on 'D <= None'.
- the CI harness now schema-validates the shipped GovernedLoop contract FIRST (like the repo's
  other validators), so contract drift fails with a clear conformance error not a KeyError.
- added two teeth: contract:conforms and unsupported-measure:refused. All 5 pass.
mdheller added a commit that referenced this pull request Aug 2, 2026
…op (task #13) (#256)

* feat(vocab): emit conformant draft GlossaryTerms from the currency loop (task #13)

Closes the currency→proposal lifecycle. Each remediation step now emits a status:draft
GlossaryTerm artifact (result.proposedTerms) for the token it connects — directly ingestible
by ontogenesis for the 3-method alignment pass. The loop PROPOSES only: partOfSpeech, alignment,
and approval are downstream; status stays draft (the loop never self-approves). The CI harness
refuses any proposed term that doesn't conform to GlossaryTerm.json or that isn't status:draft,
so the loop can't push malformed or self-approved vocab into the glossary. 6 teeth.

* fix(vocab): use .get('id') in self-approved check so a malformed proposal reports, not crashes (Copilot #256)

A proposal missing 'id' is exactly what the conformance check should surface; using t['id']
(computed before the 'if bad' branch) would KeyError-crash first. Use t.get('id').
mdheller added a commit that referenced this pull request Aug 2, 2026
… vocab (task #13) (#257)

* feat(vocab): LIVE dogfood of the currency loop on the estate's own vocab (task #13)

tools/dogfood_vocab_currency.py runs the governed loop over THIS repo's real vocabulary — every
approved GlossaryTerm in examples/ (fixed/LSA) vs specs/*.md (open/LDA) — answering honestly
whether our approved vocab is current with our specs. It is not: 2 approved terms vs 14 specs,
divergence ~0.98, and the governed loop escalates-human (can't reach currency in its 8-iteration
bound) with concrete draft proposals rather than pretending currency. Writes each proposed draft
GlossaryTerm to build/vocab-currency-proposals/<slug>.json (gitignored) — the artifacts ontogenesis
ingests. Informational (make vocab-currency-dogfood, exit 0, NOT in the validate gate); corpus is
Markdown-cleaned + generic-prose stoplisted so domain terms surface (contract/runtime/schema/
policy/device/mpcc/slot). Enforcement stays on fixtures in validate-vocab-currency-loop.

* fix(vocab): remediate Copilot review on #257 — robustness + de-hardcode metrics

- real_corpus() now skips an unreadable/undecodable spec with a warning instead of crashing.
- real_glossary() requires type==GlossaryTerm (or untyped bundle entry), so a doc merely
  carrying a glossary-namespaced id but typed otherwise isn't miscounted as a term.
- reworded the 'always exits 0' claim to its honest meaning: it doesn't gate on the currency
  OUTCOME (exit 0 whether current or escalated); a genuine IO error still surfaces.
- softened volatile hard-coded metrics (term/spec counts, ~0.98 divergence) in the spec +
  CHANGELOG to qualitative statements marked point-in-time.
mdheller added a commit that referenced this pull request Aug 2, 2026
…od alignment pass (task #13) (#258)

* feat(vocab): governed draft->approved glossary promotion — the 3-method alignment pass (task #13)

Closes the vocabulary lifecycle. tools/promote_glossary_term.py promotes a draft GlossaryTerm to
approved ONLY when the 3-method alignment holds as a fail-closed MEET: capture (ontologyClassRef)
AND vector-align (vectorLink pinned to the sovereign nomic/768 space AND reciprocated by the named
peer) AND implement (estateBinding entity/service/action). It recomputes the meet (never trusts a
declared flag), refuses on any missing/non-reciprocal/off-space method (term stays draft — no
governance hole), and its approved output PASSES the #250 alignment drift-guard by construction.

validate-glossary-promotion (in make validate) teeth: full alignment promotes; missing-implement,
non-reciprocal peer, and off-sovereign-space embedding are each refused; promoted output passes
the reused #250 check_alignment. Example: governed-loop <-> operational-dag reciprocal pair.

* fix(vocab): guard promotion input — refuse non-term + already-approved (own review)

promote() now refuses a non-GlossaryTerm input and an already-approved term (no silent mutation),
and uses .get('id') so malformed input can't KeyError. Two teeth added; 7 total.

* fix(vocab): remediate Copilot review on #258 — cosine + schema-valid output + guard filter

- promote() now requires vectorLink.cosine (number in [-1,1], schema-required) AND validates the
  minted approved term against GlossaryTerm.json — it can never produce a schema-invalid approved
  term (fail-closed on the output, not just the alignment content).
- the #250-guard teeth keyed the terms dict by 'approved'/'peer', but check_alignment prefixes
  messages with the dict key, so a real failure (e.g. non-reciprocal) whose message lacked the
  term id could slip the filter. Now keyed by id; filter catches any failure prefixed with the id.
- added teeth: missing-cosine refused + a regression test proving the #250-guard filter catches a
  holey approved term. 9 teeth total.
mdheller added a commit that referenced this pull request Aug 2, 2026
…h (task #13) (#259)

* feat(vocab): agreement test — glossary relations vs blast-radius graph (task #13)

The neurosymbolic check that the vocabulary tells the truth about structure. tools/agreement_test.py
projects the glossary's composition relations (has-a/has-member) onto the estate via
alignment.estateBinding and compares them to a CONSUMED blast-radius/dependency graph (GBRG owns
that graph; this only compares). Fail-closed on OVERCLAIM (a declared dependency with no observed
edge = governance hole); reports DRIFT (an observed edge no relation names) as a remediation
candidate (a proposed has-a relation), same treatment as the vocab-currency loop's candidate terms.
Only composition predicates imply runtime dependencies; is-a/skos:* excluded.

validate-agreement (in make validate) teeth: aligned agrees; overclaim refused; drift reported as
candidate.

* fix(vocab): agreement test only holds APPROVED terms (own review)

A draft term hasn't passed the alignment gate and doesn't regulate state, so its relations must
not overclaim the estate. Restrict declared edges + bindings to status==approved terms.

* fix(vocab): fail-closed on ambiguous bindings + malformed graph edges (Copilot #259)

- two approved terms binding the SAME estate entity made drift attribution ambiguous (dict silently
  dropped a duplicate). Now surfaced as bindingConflicts and fails closed; only unambiguously-bound
  entities participate in drift.
- a graph edge missing from/to would KeyError-crash. Now malformed edges are collected into
  malformedEdges and fail closed instead of crashing.
Two teeth added (ambiguous-binding, malformed-edge); 5 total.
mdheller added a commit that referenced this pull request Aug 3, 2026
…ask #13) (#260)

* feat(vocab): stopword deviation analysis — audit the words we DROP (task #13)

The stoplist is itself ungoverned vocabulary: a word that is filler in one domain (set, class,
state, required) is a real term in another. tools/stopword_analysis.py audits the dropped words
across domains using TWO signals — cross-domain deviation (concentration) AND compositional density
(repeated-collocation rate) — because frequency alone can't tell a domain term from a stylistic
quirk (e.g. 'and' is concentrated in chatty prose but forms no repeated collocations). Surfaces
term-candidate / stylistic / noise; term-candidates are un-stoplist proposals (a remediation signal
like the currency loop's candidates and the agreement test's drift).

validate-stopword-analysis teeth: domain terms surfaced; a stylistically-concentrated word NOT
promoted (the trap frequency alone falls into); a uniform word is noise. stopword-analysis-live
audits the shipped STOP over specs/*.md (currently clean: 0 candidates / 38 noise / 9 insufficient).
Compositional density is the bigram floor of the k-gram TF-IDF/LSA differential (3..7) to follow.

* fix(vocab): remediate Copilot review on #260

- compare UNROUNDED compositional density to the threshold (rounding could flip a boundary
  verdict); round only for reporting.
- precompute a Counter per domain instead of t.count(w) per (word, domain) — O(tokens + words*domains).
- expose the three verdicts distinctly (stylisticWords/uniformNoiseWords + counts); keep the
  flattened noiseWords (their union) for existing consumers.
- validator: require >=1 term-candidate before the all(...) concentration check (all([]) is
  vacuously True and would mask an empty result).

* fix(vocab): restore repeated_adj line dropped in the prior remediation edit

The previous commit accidentally removed the repeated_adj assignment while switching to the
unrounded-density comparison, causing a NameError. Restored; all teeth + make validate green.
mdheller added a commit that referenced this pull request Aug 3, 2026
…opword candidates (task #13) (#261)

* feat(vocab): k-gram TF-IDF/LSA differential (orders 3-7) — confirm stopword candidates

The confirmation stage for the stopword deviation analysis. tools/kgram_tfidf_differential.py
measures a candidate word's domain-specificity across n-gram orders 3..7: per order it builds the
n-gram x domain TF-IDF matrix (n-grams over raw tokens, so phrase-embedded stopwords are captured),
takes the LSA (truncated SVD) top component as the cross-domain axis, and scores the candidate by
its strongest domain-specific n-gram DISCOUNTED by intrinsic unigram specificity (so 'the' can't
borrow 'the state machine' specificity). The differential across orders is the discriminator: a
true term PERSISTS (confirmed-term); a concentrated-but-diffuse word ('and') or a borrowed-signal
word ('the') stays unconfirmed.

validate-kgram-differential teeth: set/class/state confirmed across 3..7; 'and' unconfirmed;
'the' stripped by the unigram discount; orders are exactly [3,4,5,6,7]. Uses numpy (installed in
the make recipe). Closes the two-stage stopword design.

* fix(vocab): remediate Copilot review on #261 — LSA in verdict, perf, <2-domain guard

- confirmation now uses BOTH signals of the differential (docstring promised it): TF-IDF must
  persist across orders AND the word must load on the dominant cross-domain LSA axis
  (lsaParticipates). Matches 'tf-idf lsa differential'; 'and' now fails both signals.
- precompute candidate->row indices in the single pass over vocab instead of rescanning the whole
  vocab per candidate (O(vocab) once, not O(vocab*candidates) per order).
- differential() raises on <2 domains (TF-IDF/LSA are cross-domain measures — fail fast, don't
  emit a meaningless result). Teeth: single-domain rejected. 6 teeth total.
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