Skip to content

Sync dwolfson/trellis main: /next UI fidelity, vendored-code rule, coverage audit, dependency_support, compiler gap/cap fixes - #61

Merged
dwolfson merged 66 commits into
odpi:mainfrom
dwolfson:main
Sep 13, 2026
Merged

dwolfson merged 66 commits into
odpi:mainfrom
dwolfson:main

Conversation

@dwolfson

Copy link
Copy Markdown
Member

Brings odpi/egeria-trellis main up to date with dwolfson/trellis main since #59. Fast-forward: the fork is a strict superset (0 commits behind upstream). 34 commits (plus merge commits) via 23 fork PRs, 76 files, +7,090/−355 — all in packages/resource-explorer plus one CI workflow change. Several working sessions; each fork PR carries its own reasoning.

The /next experimental UI

Enrichment fidelity (#34, #45), the Curate pane — the plan, the record, and the commit (#51, #52), the journal — why a resource matters, written to be read by someone else (#35, #48), member trees and promotion from a member list (#33, #41), analysis-pane editorial and a second dashboard round (#31, #32), review fixes (#44).

Vendored code is not this repository's

One rule read by every whole-tree walk, recorded on the inventory as a flag rather than by dropping rows (#36, #39, #47). Every walk under surveyors/ and ingestion/ now consults it, with a guard that fails naming any walk that does not. On egeria-workspaces this took symbols 26,564 → 5,910 and files 6,423 → 1,998 on re-survey; other repos keep their pre-#36 figures until re-surveyed.

Coverage, and the first gap question answered

  • Coverage audit (Re/docs consolidation part 2 #40): questions ↔ analyses ↔ steps ↔ surveys, measured against the live registry and read against two owner decisions — analyses serve ad-hoc Chat questions as well as catalogued ones, and the goal is the right surveys per repo's investigation intent, not every survey everywhere. The wiring is sound; the gap is on the 14 repos chosen to keep investigating.
  • dependency_support (pyegeria 6.1.10: reports run as the signed-in user; agentstack shim for mcp 2; deferral notes #46): "Do we already support these dependencies?" moves from human to mixed. Measured first: Egeria's 213 technology types matched 2 of 2,894 dependency names by exact name, and token overlap matched 373 that were nearly all wrong — so the knowledge lives in a curated mapping (configdata/dependency_support.yaml) with a strict loader, each entry linked to the Egeria type it will promote into ("A now, shaped to seed B"). Three states kept apart: matched / unmatched ("not yet classified", never "unsupported") / Egeria-could-not-check. Also fixes three pre-existing latent NameErrors in the adapter.

The compiler

Operational

Testing

resource-explorer suite green on the fork's main at this PR's head (19f3733, 4,44x passed). The run history since #59 is honest rather than uniformly green, and worth stating: 964d0e2/04eacb7 were red on one test_context_compile case because #46 merged before the #50 that fixes it (reverse of the stated order, ~50s apart); 7b63cc1 was red on one annotation check-name collision introduced by #51, fixed two commits later by #52. Every red was fixed by the next merge; the head is what was verified.

Known open, deliberately

  • architecture_doc_lens and architecture_summary readers synthesise defaults from an empty registry and so pack sections for an unsurveyed repo — a find-absence-as-answer defect, to be fixed in the adapter separately.
  • The question_catalog.yaml-matches-CSV regeneration guard is coming in a follow-up.
  • No freshness check before dispatch of a derived analysis (owner decision: not now).

🤖 Generated with Claude Code

dwolfson and others added 30 commits September 11, 2026 12:56
…rate

The 2026-09-11 annotation-type rename ran an unconditional UPDATE on
project_published_annotation_types inside _init_schema, before CREATE INDEX
IF NOT EXISTS on the same table. Under Postgres an UPDATE takes
RowExclusiveLock even when it matches no rows, CREATE INDEX wants ShareLock,
and two processes opening the registry at once each held the first while
waiting for the second. The answer layer opens one registry per question,
sixteen at a time, so the Dashboard by question hit it on every load: 500s
on most answers, and a pane reporting "not answered — 0 of 1 reported"
over measurements that were there.

Reproduced with sixteen parallel opens against the live registry: 14 of 16
deadlocked. With this change: 16 of 16 clean.

The migration now reads first and updates only when a stale row exists —
which is no process after the first — and sits after the index, so lock
order matches. A test pins the shape SQLite can check: a fresh init issues
no UPDATE against that table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
DASHBOARD-ROUND-TWO.md, all five rules, on the by-question view. The
grouping by question stood; what was missing was its payoff — a reader
still assembled every answer from raw rows, five times, down the page.

1. Every section opens with its answer, relayed from the answer layer — the
   same sentence the Evidence rail shows, not one composed here. A question
   the layer cannot answer says "not answered — 0 of 3 analyses reported",
   which is a state worth seeing. A human-answered question shows what the
   person said, or says that nobody has, rather than pretending its
   informant analyses decide it.
2. Findings that agree collapse to one row with a count and the check names,
   the full rows behind a disclosure. Unresolved ones stay individual and
   first: each is a decision.
3. Provenance is section-level — "3 analyses · 31 measurements · latest 8d
   ago". The analysis name leaves every row; it is in the popup beside the
   run, which is where it matters.
4. A disagreement is the section's headline, both values side by side with
   the analyses that reported them, above the detail. Shown once: a second
   question that reaches the same conflict says so and links to the first.
   The by-question view had lost this marking entirely; it is back.
5. Booleans leave the counts table for the answer line; unsets render as
   "not recorded: …", never as a number. A zero stays a zero — whether it
   means none or not-set is the producer's to say.

Plus an anchor rail — every question with its state glyph, jump to any —
and the header sentence "N of these are not built in /next" removed: review-
speak, unreadable in the product, and the dashed tab already says it.

The detail under each section is collapsed by default. 4,811px for sixteen
questions and the unasked section, from roughly double.

Also carries the _init_schema deadlock fix (#30), without which this pane
could not be verified: the answer layer opens one registry per question and
sixteen at once deadlocked on the unconditional migration UPDATE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Dashboard round two: the answer tier the regrouping was meant to unlock
Registry open must not take a write lock when there is nothing to migrate
…un-over-run movement so far

Compiler = re/compiler-catalog-caveats (2c9ca21), run from a worktree before
its PR merged; judge held at v3, seed and repos unchanged. 312 rows, 0
errors, replay 156/156. Paired on 156 questions vs run 6: answers_question
+0.13 (32 up / 15 down), supported +0.17, unsupported -0.06, cites +6
points, misread flat - every metric the right way, still inside the noise
band on the answer score until reproduced. The caveat's own words reach the
secrets answers ("against the gitleaks ruleset over the HEAD snapshot").
docling's CVE row is still a bare "Yes" under an honest headline and
caveat, which settles the residual as the 8B answerer's; two one-variable
options recorded.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…answer reads as one

Repo Handoff items 1 and 2, from the Dashboard Round Three review. Every
question carried three to five lines of accent prose between its answer and
the next question — and with everything else collapsed, that was the largest
text on the pane and the only thing on it not in the product's voice.

The prose was the catalog's Rationale/Source column, carried to the screen
two rounds ago, and it mixed two things that need different homes. The
LIMIT — what this answer does and does not cover, present tense, for a
reader deciding how far to trust it — stays in Rationale/Source and is the
caption under the answer, in the accent. The HISTORY — what used to be
wrong, which analysis landed when, what the generator could not parse —
moves to a new Catalog History column, carried through generator, YAML,
reader and route, and shown behind a muted "catalog history" disclosure.
Sixteen Analysis-stage rows were split, each keeping only what it already
said, in the tense it belongs in; nothing was added. The accent now goes to
the limit alone. Two tests pin the split: no Analysis limit reads as a
changelog, and history is carried only where it was authored.

The composition bug: several analyses' sentences joined with a space read
as one broken sentence — "all 3 checks pass one contributor writes most of
the code". Joined on " · " now. The separator makes the boundaries visible
without deciding how the claims combine, which is the judgement still owed.

The misleading tick: "Is there a current, published, security analysis?"
showed ✓ over prose that said nothing detects one. The catalog calls that
question `mixed`; a mixed or partial question whose analyses reported is
now ◐ in the accent — attention, not completion — rather than ✓. Generic,
not special-cased.

History shows its changes, not its rows: the measurement popup leads with
the transitions — "57 08-07 → 59 08-24 → 68 09-01 · 30 runs, 2 changes" —
and keeps every run one click on. Thirty rows of which twenty-eight were
identical hid the two steps that mattered.

And the smaller things: the summary line no longer offers two numbers that
cannot partition one set ("27 questions at this stage · 16 measured · 11
answered another way"); the "was 2 20d ago" delta is gone from finding rows,
where a previous count under a pass/fail is not a verdict; zero counts are
dropped under an analysis whose state is nothing-found, since its sentence
already said it; the unasked analyses show their sentence with the ledger
behind a disclosure; and separation — a hairline with air above every
question, the glyph in the margin, weight in the order answer, question,
meta.

4,288px at 1280 wide, from 4,811.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Analysis pane, finished: the limit stays, the history moves, and the answer reads as one
…ack button returns

Repo Handoff item 3, "the missing third door". There were three "show me
more" requests on the dashboard and only one had a path. About the
MEASUREMENT — where did 68 come from, has it changed — is the popup. A
question nobody asked is chat. The one in the middle, and the most common:
WHICH 68? Which 18 advisories, which 90 files, which three sub-resources
were not worthy? Nothing opened that. The popup looked like the door and
opened the number's history, so a reader learned that 18 had been 18 for a
fortnight.

Members render in the right rail, which on Analysis held an empty ask box
and eighteen hundred pixels of nothing. Click a count, the rail lists the
members grouped; the centre keeps its place. Read from the registry, not
the display-shaped results, because those drop detail_json and that is
where cve_scan keeps its advisory ids — the designer's "check first" was
right: the members were there, the readers dropped them.

Members nest — file → symbol today — and purpose sets the default scope:
intent to use wants the public surface, intent to maintain wants that plus
the internal structure. One tree, two expansions, because maintain is a
superset. Only symbols carry a public/internal marker, inferred from
naming; the rail says so, and says when a scope was asked for and not
applicable.

It settles the disagreements, which was the instruction on the block that
could not be followed: `total` 68 is dependencies (52 python, 9 java, 7
javascript); `total` 90 is data files (76 CSV, 13 Excel, 1 Parquet). Two
analyses sharing a metric NAME, not a measurement. Two clicks.

And it found a defect on first open, as the designer predicted a member
list would: the top symbol files on egeria-workspaces are
node_modules/typescript/lib/typescript.js. The 26,563 "symbols" are mostly
a vendored library counted as the repository's own. An ingestion matter,
reported rather than fixed here.

The floating Feedback button is back, rebuilt for skin 1c on the existing
/api/feedback route: bordered not filled, no emoji, rating as ○/●, a 401
answered with "Sign in to send feedback" rather than a generic error. One
fix over the first cut: the overlay carried the `flex` utility, whose
display:flex outranks preflight's [hidden], so it never closed; it toggles
the `hidden` class now, which Tailwind orders last.

Also rebuilds tailwind-next.css. Two classes from the previous rounds —
pl-[22px] and scroll-mt-[8px], the separation indent and the anchor
offset — were never compiled, so those rules were not rendering on main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…ration classes it added

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Member trees: every count opens the things it counted — and the feedback button returns
Repo Handoff item 4. Two halves, not one list of eight fields with one
Save. The line between them is not arbitrary: a field is a JUDGEMENT if a
person's opinion is the value — sensitivity, criticality, intended use,
actual use, owner — and an OBSERVATION if a person is supplying a fact
about the world — licence, environment, retention. Opinions need an
author, a date and a review state. Observations need a source.

Judgements come first and get the room; they are the part only a person
can supply. Every field saves alone: someone who knows the owner and not
the sensitivity can say so and leave. The author and date sit on every
judgement without hovering, because testimony without an author is not
testimony.

The author is stamped by the SERVER from the signed-in identity, never
taken from the client — the write model has no author field to forge, and
an anonymous write is refused with 401 rather than recorded as nobody's.
PATCH .../field does the read-modify-write on the server, so two people
setting two fields do not clobber each other the way two whole-document
POSTs would. The five fields the old UI's form also reads are mirrored to
its flat keys, so the two UIs keep agreeing.

Perishability, which needs no per-field configuration: a judgement carries
the measurements that were on screen when it was made. When one of those
moves, the judgement is not invalidated — it gets a flag that says what
moved: "⚠ review — evidence moved: cve_scan". A specific, answerable
prompt, not a staleness timer. The rail marks the same event from the
other side: "new since you judged" on the analysis that moved.

Evidence sits in the rail as material, never as proposals. No "apply
suggestion". The one exception is a fact a survey already established —
the licence — offered to confirm, with its source, into the observations
half. It offers "Apache License 2.0", the licence, not "permissive", its
risk tier; the finding's label is the tier and the fact is before the
dash.

The seven Human-Supplied catalog questions render under both halves as
"what only you can answer", each saving alone as before. And the one
sentence worth pre-empting is on the pane: nothing here is written to the
catalogue until Curate.

Not built: owner candidates from the contributor data ("2 candidates ›").
The interim owner is the investigator, marked so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Enrichment: testimony, not paperwork
Repo Handoff item 5 — small, standalone, and the only part of curation
that was absent rather than imperfect. Everything else RE holds about a
resource is written to be CORRECT: a measurement, a verdict, a field with
an author. A journal entry is written to be READ — prose, dated, addressed
to someone. Curation is not only getting the record right; it is advocacy,
and a curator who catalogues a resource accurately and tells nobody has
done half the job.

Append-only. An entry is a statement someone made on a date; later events
do not invalidate it, they get a later entry. So it sits outside the
durable/perishable split entirely — no review flags, nothing to reconcile,
never overwritten. The author is the signed-in user, stamped by the
server; the payload has no author field to forge, and anonymous is refused
rather than recorded as nobody's.

Suggestion is a routing question and perspectives answer it: a note on a
data-heavy repo is for Data Experts and Consumers, the same tags on every
question row. So suggesting is "pick a perspective, or pick a person". And
it arrives as a WORK-LIST ENTRY for them, not a notification — one list
per audience, "Suggested to Data Expert", created on first use, the
resource as a member with the entry as its rationale. Work lists already
exist, carry counts, and survive being ignored for a fortnight; a
notification is a thing you dismiss. A second suggestion of the same
resource to the same audience updates the rationale rather than adding a
row; the journal keeps every entry regardless. The UI says where the
suggestion landed, not "sent".

Lives on the Disposition sub-tab, now built: the verdict picker and its
dated trail — "tracking · 32d ago" — above, the journal beneath, because
the journal is the prose companion to the dated record. Nothing prompts
for an entry at cataloguing time and nothing blocks on one: advocacy
written to satisfy a required field is "useful library" on two hundred
assets. The empty state is visible instead — "Nobody has written about
this resource yet" — which is a fair thing for a corpus view to count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
… recorded on the inventory

The designer's question after the member tree opened on
node_modules/typescript/lib/typescript.js: does the file inventory exclude
node_modules, vendor, .venv, dist, site-packages? It did not. The exclusion
list existed — in line_census.py, private, read by the line census alone —
so "how much code is there" was answered honestly while every other count
was not. Measured on egeria-workspaces: 68% of the file inventory, 77% of
the extracted symbols and 99% of the JavaScript chunks in the vector store
were TypeScript's vendored library counted as the repository's own. The
symbol count, the documentation ratio, the public/internal split the scope
toggle had just been built on, the component graph that feeds Curate, and
chat's citations all shared an inflated denominator — and chat would cite
somebody else's library as this repository's, with a citation, which is
worse than a wrong answer because it looks checkable.

The rule moves to ingestion/vendored.py and every walk reads it: the
symbol extractor, the RAG code and markdown ingestion, the data profiler,
the file count, and the line census by the same name it had. Only
directory segments are tested; a file called `vendor` is not vendored and a
file inside `vendor/` is.

The inventory RECORDS provenance rather than dropping the files — a new
`vendored` column, stamped on write, DEFAULT 0 so an index made before this
change is unchanged until re-indexed, and file_inventory_summary() so a
surface can say "6,423 files · 4,425 vendored" — the honest number one
click from the misleading one, not a silent replacement. Readers exclude
vendored rows by default. The one exception that matters: the sub-resource
survey reads the whole inventory and lists a vendored folder as
"not worthy · vendored", because omitting it would make "34 sub-resources"
silently mean "34 of 41". Its existing test — not-worthy folders are still
listed, not omitted — is what caught the first version hiding it.

The member tree's symbol note now says which: either the own/vendored split
when the inventory carries it, or the index date and that an index before
2026-09-11 counts vendored code as the repository's own.

What this commit does NOT do: re-index anything. The numbers on every screen
are unchanged until each repository is re-surveyed, and that is a write to
the shared registry and the vector store — the owner's call, one repo
first, then look at the pane.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
The journal: why a resource matters, written to be read by someone else
Vendored code is not this repository's: one rule, read by every walk, recorded on the inventory
The one variable of experiment run 8. Runs 5-7 gave the answering model an
honest cve_scan headline ("not checked: 0 of 61 declared dependenc(ies)
could be queried") and, from run 7, the catalog's own caveat directly under
it - and the 8B answerer still replied "No", then "Yes", to "Are there
outstanding CVEs?" on docling. Three compilers' worth of evidence did not
move a one-word answer, so the instructions (FULL and SUMMARY rungs; the
bare rung is the budget floor and is unchanged) now say: give the yes or no
and then the analysis, the value, and any coverage limit or caveat shown
beside it; a bare yes or no is not an answer. Measured by run 8 against run
7; unmeasured until then.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE experiment: run 7 — the catalog caveat beside the evidence; best r…
Re-surveying egeria-workspaces after #36 gave the honest inventory —
1,998 own files of 6,423 — and then put every vendored symbol back:
26,564 again, one survey after the profile refresh had taken it to 5,910.
The survey step's symbol extractor carries its own copy of
IngestionPipeline._local_files, "duplicated rather than imported" by its
own comment, and #36 fixed the original and not the copy.

Four walks that measure the repository's own content now read the rule:
the survey step's symbol extractor (the one that regressed), manifest
detection (a vendored dependency's package.json is not this repository's
manifest), the conventions parser (doc_breadth counted node_modules'
markdown — 175 of egeria-workspaces' .md files), and the PDF walks.
Architecture recovery keeps its own exclusion module and dependency_parser
its ad-hoc vendor skip; both are allowlisted by name.

And the guard: a test that lists every whole-tree walk under surveyors/
and ingestion/ and requires each file to consult the rule or be on that
short allowlist. Made to fail on purpose first — renaming the call in one
file names that file — so it is known to fire, not assumed to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
The walk #36 missed, and a guard so the next one cannot hide
…sured against intent

A coverage audit the project owner asked for, measured against the live
registry (61 repos) and the catalogs at 004cb66. Every number came from a
query, not a read; the audit scripts are recorded in the doc's provenance.

The wiring is sound: zero dangling analysis ids, zero unrunnable analyses, zero
question-answering analyses that have never run, every registered step in a
survey definition. The coverage question is therefore not "does the chain work".

Two framing decisions from the project owner reshaped this from its first
draft, and both are recorded at the top so the numbers are read against the
right yardstick. Analyses serve two purposes, not one - the catalogued
questions AND ad-hoc Chat questions of the kind Egeria Advisor showed people
actually ask - so manifest_parse and code_symbol_extraction are not orphans but
among the most load-bearing analyses in the catalog. And the goal is not to run
every survey on every repo but the right surveys to meet the intent of
investigating one; a repo that Scouting shows to be uninteresting is stopped.

Measured against that yardstick - the 14 repos a person chose to keep
investigating - the gap is concentrated in exactly the tiers a keep decision is
meant to unlock: 13 of 14 cannot answer IP provenance or telemetry, 12 of 14
secrets handling, 9 of 14 CVEs, dependencies, Scorecard or authorship
concentration. Discovery and Scouting questions are answerable on most of them.
The funnel is respecting its stops: 9 deep-tier runs on the 2 stopped repos.
The 44 repos with no disposition are pending, not decided - the owner has not
analysed them yet, which says nothing about whether they are interesting.

Questions with no survey or analytic at all: three (similar repos, upgrade
process, AI/ML licensing constraints), plus one partial. The other seven with
no machine answer are human by design.

Seven improvements in order, the first two needing no code. What the audit
cannot see is stated: answer quality, chat coverage itself, Egeria-side
analyses, and non-repo resources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…it cannot see

The audit's reach numbers are presence-of-a-run and unaffected, but #36 and
#39 changed what 'own code' means for every content measure, and only one repo
has been re-surveyed onto the new regime. A reader extending this into answer
content needs to know that before comparing repos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…work, not one

Records the project owner's 2026-09-11 directions on the questions with no
survey or analytic. 'Do we already support these dependencies' has an Egeria
starting point - project_dependencies cross-referenced against what Egeria
already catalogues - that still needs corroboration and augmentation by
people, so it is machine-informed and human-confirmed rather than human. 'What
kinds of integrations' is derivable from data already held - client-library
dependencies, API surface, compose/config - incomplete but useful. 'What are
similar repos' needs analysis and design work before anything is built.

Recommendation 6 splits the same way. Nothing is built; this records the
direction so the next person does not treat the three as one bucket.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…cts on

A member list is the first place in the product where a person looks at
THINGS rather than NUMBERS, and things are what you act on. "18" is not a
work item; "sixteen of these are high" is. The list was a selection; the
action was not on it. Now it is.

The filters are the selection. A facet — a severity, a package — selects
its members in one click; hand-picking stays. Facets come from the fields
the annotation already stores, and nothing is invented: "no fix" is not a
facet because cve_scan does not record fix availability, and a facet the
data cannot back would select nothing and look broken. Reported rather
than faked.

Three acts, and they are different. Add to work list is "I will deal with
this"; raise RFA is "someone must"; note in journal is "worth knowing" —
no obligation attached, so the likeliest used, and cheap now the journal
exists. One provenance line, composed on the server, travels with all
three: "16 of 19 advisories · high · from cve_scan, run 2026-09-11:
GHSA-…". The selection is a snapshot of names, never a query — a work item
that changes what it refers to when the scan re-runs is unusable; five next
time is a new observation against the same item. The name proposes itself
from the facet and the count — "egeria-workspaces — 16 advisories, high" —
and is editable before saving. Signed-in only, for all three: an item
needs someone to have made it.

Deliberately absent: "ignore" / "accept risk". That is a disposition on the
finding — a judgement with an author and a date — and belongs to the
perishable-field machinery, not a toolbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Promotion from a member list: the selection becomes a thing someone acts on
… mid-run scheduled re-survey and a catalog rewrite, negative on the aggregate

Branch re/compiler-yes-no-evidence-line (298ff67), run from a worktree.
312 rows, 0 errors, replay 156/156 - but only 2 of 156 compiles share run
7's evidence apart from the new sentence. Two causes, checked: the Automate
scheduler's nightly RepoRefreshSurvey ran on docling (23:12) and
egeria_python_git (23:45) inside run 8's own window, and the catalog's
caveat texts were rewritten on main (63f498e), which moves ranking. kafka
changed only the second way.

On the three CVE rows the sentence was for, it worked (egeria cites the
click advisory, kafka gives value plus caveat, docling states the coverage).
On the aggregate: supported claims -0.49 paired, answers_question -0.07,
median answer length 146 -> 112 chars, with the loss on non-yes/no
questions where the 8B model became terse. Plausibly the sentence priming
answer-then-one-line everywhere; not attributable under the confound.

Decision: branch not merged. Next: a within-run A/B, two compiled arms
answered back-to-back per question in one process, instruction variant as a
compile parameter, schedules for the three repos paused for the window.
Rubric v4 rides along.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE experiment: run 8 — yes/no evidence-line sentence; confounded by a…
…st:5025

test_a_malformed_usage_dict_does_not_propagate forced the reachability
probe true and left the real MLflow REST client pointed at the default
tracking URI. That is a running server on a dev box and nothing on the
CI runner, where urllib3's retry backoff ran past the 120s pytest timeout
-- every main run of the resource-explorer workflow since 2026-09-11
12:06 failed on it, masking whatever else might fail.

The test now stubs get_config with a tmp_path file store, so the bad
dict reaches the sink and the assertion (it must not raise) is actually
exercised, offline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE compiler: a yes/no answer must carry the evidence line it rests on
RE docs: coverage audit — questions, analyses, steps and surveys, measured against intent
dwolfson and others added 28 commits September 12, 2026 15:01
…icate, as a starting point for people

The first of the coverage audit's gap questions to get an analytic. Two
project-owner directions shape it: Egeria may hold "a starting point that
would still need corroboration and augmentation by people" (2026-09-11), and
"A now, shaped to seed B" (2026-09-12) - a curated mapping in RE, each entry
linked to the Egeria technology type it will later promote into.

Measured before building, and the measurement changed the design. Against the
2,894 distinct dependency names in the registry, Egeria's 213 technology types
matched TWO by exact name; token overlap matched 373, nearly all wrong -
apache_atlas to Apache Airflow via "apache", @babel/plugin-proposal-function-
bind to "Unity Catalog Function". Both of Egeria's candidate sources are
overwhelmingly Egeria describing itself; the "catalog of already-supported
dependencies" the question catalog assumed does not yet exist. psycopg2 to
PostgreSQL is knowledge, not similarity, so the knowledge is a file people
curate: configdata/dependency_support.yaml, 20 technologies, strict loader -
unknown keys, duplicates and any pattern under three characters are errors,
because a two-letter pattern is exactly how "apache" matched Airflow 105 times.

Three states, kept apart on purpose. A dependency that MATCHED - this repo
indicates X. One that matched NOTHING - no curated technology corresponds to
this name, which is almost always "nobody has classified it yet" and never
"unsupported". And a technology whose Egeria type could NOT BE CHECKED - Egeria
unreachable, which is not the same as absent. The coverage row carries the
Egeria check state at the top. Unmatched names are re-derived at read time
against today's mapping rather than stored, so a stale "unclassified" cannot
outlive the curation that classified it, and the cross-repo ranked queue - the
actual output people act on - needs no table of its own.

The question "Do we already support these dependencies?" moves from human to
mixed: machine-informed, human-confirmed. The step is Discovery tier - it reads
project_dependencies and fetches nothing from the repository; the one Egeria
call is catalog consultation. Added to RepoDiscoverySurvey, and to the
Discovery allowlist in test_analysis_catalog_reader with the reason.

Verified live on egeria_python_git: 32 dependencies, PostgreSQL / Jupyter /
pyegeria indicated and all three present in Egeria (211 types checked), 29
unclassified and listed. Across 51 repos with dependency data: PostgreSQL on
16, Jupyter and Redis on 10, Docker and Kubernetes on 9. The top of the
curation queue - pydantic (27 repos), pandas (23), requests (23), pytest (22) -
are libraries rather than technologies, which is a curation decision the
mapping can express either way and is recorded as open.

Two faults found by running it rather than reading it. The results reader
first read a `detail` key where query_findings hands back `detail_json` as a
string, so every technology came back unchecked with no dependencies; only the
live read-back showed it, and a test now pins the parse. And the adapter used
`log` at three STEP-introspection warning sites without ever defining it -
three latent NameErrors since they were written; adding a fourth use surfaced
all four under ruff, and one module-level line fixes them.

33 new tests. Five guards made to fail on purpose: Egeria-unreachable reading
as absent, the three-character floor, a greedy apache* pattern in the seed,
the detail_json parse, and the no-dependencies path reporting a normal zero.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…e, say which, and drop the orphan collections

Designer review of 2026-09-12, section 5, and two of the answered items.

VENDORED_DIRS conflated dist/build/target/.git with node_modules and
site-packages, so the rail said "vendored and not counted" about a
repository's own build output. Two sets now -- VENDORED_DIRS (somebody
else's code checked in) and GENERATED_DIRS (our own output) -- with
SKIPPED_DIRS as the union every walk reads. The inventory column stores
which (1/2), the summary reports both, and the rail sentence says
"4,425 vendored and 12 generated, not counted". is_vendored() keeps its
name and meaning. The short form the commit message promised and app.js
never rendered -- "6,423 files . 4,425 vendored" -- now sits on the
members rail heading.

is_vendored_abs raised nothing on a path outside its root and returned
False for every file; it raises. vendored_tops marked all of src/ not
worthy when one src/x/node_modules/y.js existed; folders are judged by
their own name. foss_scorecard and interface_surface carried their own
copy of the reader's filter in raw SQL; they go through
registry.get_file_inventory.

documentation_coverage's headline: "40.4% of the public API documented"
over a mostly-TypeScript repository read as a claim about the
repository. It now says "40.4% of public Java and Python symbols carry a
docstring (971 measured; other languages not measured)" -- the
denominator was never the file inventory, as the review established.

The six orphan egeria_workspaces_* collections (12,515 chunks, the
vendored-citation hazard) were dropped on the owner's instruction after
checking 69 registry columns for a reference; recorded in
docs/vendored-guard.md rather than left to disappear.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE /next: Enrichment fidelity — source renders, owner marked, all seven questions arrive, licence confirm gated
…ith its trail, the landing named, the audience whole

Designer review of 2026-09-12, section 4.

Two trail formats for one fact: app.js rendered the spec's line and
worklist.js a two-line layout of its own, with no tnum on the column that
exists to align. One formatter in format.js now serves both, and the
reason sits in ink beside the verdict it explains rather than muted.
"Changed N time(s)" is a real plural, and the pane's trail carries the
count the matrix had.

The Disposition pane had Verdicts above a trail with no way to add to
it; the only picker was the header popover, a click away and above the
heading. The picker is one function, shown in both places with its trail.

The journal's confirmation named the slug, not the list, and removed
itself after six seconds -- the only record the writer gets. It names
the list ("Suggested to Data Expert", returned by the server alongside
the slug) and stays until the next write.

The work-list view replaced the sub-tab rail with "Work list . back",
breaking the identical-order rule on that one view. The rail is present
and its per-resource tabs marked as such.

The journal's audiences came from list_perspectives(), a deliberate
subset of what can narrow a filter. An audience is the whole vocabulary:
/api/analyses/perspectives?scope=all returns EGERIA_PERSPECTIVES and the
journal reads that. Same twelve today; the point is which list it
follows when they diverge.

Also: a guard that the committed question_catalog.yaml is what its CSV
generates -- a peer's hand-edit passed 144 tests today because nothing
checked it; the survey definitions have had the equivalent since their
generator landed. Made to fail on purpose before trusting it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…in-run A/B; kept as a variant

Run 9 (468 rows, three conditions answered back-to-back per question,
schedules paused, rubric v4, replay 156/156 in both compiled arms) measured
PR #38's sentence on its own: paired against the same wording without it,
answers_question -0.06, supported claims -0.58 (32 up / 53 down), median
answer 114 vs 181 chars, bare one-word answers 15 vs 10. Terser on every
kind of question, not only yes/no ones.

Two mechanisms, separated: instructions are a REQUIRED section, so the
sentence's 202 characters came out of the evidence - one section dropped a
rung in 135 of 156 compiles (1275 vs 1396 sections at FULL); and on the 21
identically packed pairs it still cost -0.14 supported and -0.10 answers.
It gained on two of three CVE rows. Rule established and pinned by a test:
an instruction has to beat the rung it displaces.

Production wording is plain again; the sentence survives as
INSTRUCTION_VARIANTS["yesno_line"] / harness condition compiled+yesno for a
retest against a larger answering model. The v4 rubric also stops marking
RAG's grounded declines as 0 (rag answers_question 0.49 vs 0.24-0.31 under
v3) and now counts absence-over-unchecked as a misread (rag 0.15).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…s generated from

question_catalog.yaml is generated from docs/dr-egeria/resource_questions.csv,
and its own header says so - "edit that CSV, then regenerate this file, don't
hand-edit it directly". The previous commit hand-edited it. The change was
right and the artifact was wrong: the next regeneration would have erased it.
Caught by the /next session in review, not by me and not by any test.

Fixed by the generator's own convention, on top of #45's stage change rather
than instead of it. The row's Answering Analysis cell now reads "MIXED:
dependency_support (...) + human-supplied corroboration via the Enrichment
Context form", which the generator parses to kind: mixed, analysis_ids:
[dependency_support]; its Catalog History gains a line saying when and why.
Regenerated: the only lines that differ from #45's regeneration are this one
question's note and history, which is the proof the generator is deterministic
and the rest of the file was already in sync.

Three guards fired on the regeneration and each was right. check_registry.yaml
must classify every analysis, and dependency_support belongs in
instance_keyed_not_checks - one finding per matched technology, the same shape
as cve_scan's one-per-package. And two hard-coded counts in the scouting-
questions route test moved by exactly one: the question now carries an analysis
id, so `measured` is 17 not 16, and it already carried history from its stage
change, so `with_history` is 16 not 15. Both updated with the reason written
beside them rather than adjusted to pass.

The suite has no guard that question_catalog.yaml matches its CSV - the survey
definitions have one, the question catalog does not, which is how the hand-edit
passed 144 tests. The /next session is adding that guard in its next PR; noted
on #46 so it is not built twice.

Merges origin/main (7ecb1ac) to sit on top of #45.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…e too

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
… run did not

The full suite on the branch failed four tests I had not run locally. Three
are mine and fixed here; the fourth is in the compiler and is handed to the
session whose in-flight branch owns that file.

The no-dependencies branch and the normal branch both emitted an annotation
named `coverage` with no item key, so both would publish one qualifiedName if
they ever ran in the same survey (test_annotation_check_names). They cannot
both run, but the honest fix is not an exclusivity exemption: the early return
says "nothing to assess", which is a different statement from "here is how
much was covered", and it is now named `nothing_to_assess`. The persisted
finding row is unchanged - check_name `coverage`, label `no-dependencies` -
which is what the results reader keys on.

repo_dependency_support is added to the exhaustive step-key set in
test_repo_survey_definition_adapter, which is exhaustive on purpose so a new
step is a decision, and dependency_support joins the Dependencies dashboard
beside dependency_analysis - it reads the same project_dependencies rows, so
the two are one question asked twice: what is here, and what does it mean.

The fourth failure, test_context_compile's reaches_the_answer_through_compile_
context_too, is the compiler's, and I have not touched it. Verified locally:
context_compile._has_content returns True for a `{"_status": {...}}` envelope
while facts._has_content returns False for the same dict. The compiler
therefore packs a never-run analysis as a section headed
"_status: state=never_run" rather than listing it as a gap. Every reader that
follows the _status convention is affected; this analysis tipped the count so
that, under the 12-section cap, a never-run section displaced
documentation_coverage. Diagnosis and a proposed one-line fix are with the
owning session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE /next: the journal and Disposition — one trail format, a picker with its trail, the landing named, the audience whole
context_compile._has_content counted {"_status": {"state": "never_run"}}
as content, so a never-run analysis whose reader follows the
result_status.py envelope convention was packed as a section headed
"_status: state=never_run" instead of judged as a gap. facts._has_content
has exempted the envelope keys (_status, surveyed_at, detail, scoped_to,
run_outcomes) since it was written; the compiler's copy did not, and its
docstring's "the readers do not carry that distinction" stopped being true
with architecture_diagram, architecture_recovery and dependency_support.

Surfaced by #46's CI (diagnosed and reproduced by dwolfson-4c): the new
dependency_support reader returned the envelope against an empty registry,
took a slot under the 12-section cap, displaced documentation_coverage, and
test_reaches_the_answer_through_compile_context_too lost "readme".

Fix: the same envelope key set, skipped at the top level of the results
dict. Tests: the truth table gains three envelope rows; a test pins that
the compiler's and the fact layer's checks agree; a never-run reader now
lands in gaps and never in the text. 131 tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…nds no slot

Second finding from #46's CI (dwolfson-4c, 2026-09-13): the cap was applied
to the RANKING before any analysis was resolved, so a gap had already spent
a slot. Against a sparse registry eleven of twelve slots went to gaps and
the one analysis with findings, ranked 13th, was deferred with "below the
section cap". The cap=12 sweep never saw it: on the experiment repos every
cited analysis had run.

The walk now continues down the ranking until `cap` analyses have a
candidate; gaps found on the way are still reported (they are information)
and cost nothing; what lies below the point where the cap is reached is
deferred unresolved, in rank order.

Tests: the fake registry is now a _NullMock whose unconfigured methods
return None instead of nested mocks - readers were building dicts of
"<MagicMock ...>" values that _has_content counted as content, and one
existing test (packing order vs repository_health) passed only because of
that garbage; it now gets a real finding. Three new tests: the only analysis
with findings packs however low it ranks; the cap still bounds sections
with evidence; no MagicMock text reaches a compile. 134 pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE: dependency_support — which technologies a repo's dependencies indicate, as a starting point for people
RE: vendored is a provenance claim, generated is not — split the rule, say which, drop the orphans
…ctions

RE compiler: a _status envelope is a gap, and the section cap counts packed sections
Repo Handoff item 6, the Curate wireframe. curate_plan.build_plan reads
only local state -- facts, findings, enrichment, verdicts, the
disposition -- and assembles the three columns: what it IS (Software
Library from the declared distribution, Software Capability from the
intended use, Endpoints from interface_surface with the implied/no-
contract clause, Infrastructure Asset? from Dockerfiles), what it HOLDS
(profiled data files, worthy sub-resources), what it is MADE OF
(accepted components; review stays on Architecture verdicts), how it
RELATES (dependencies; already-catalogued ones not looked up, said so),
and the manifest of what a commit writes: testimony copied as
classifications with author and date, measurements linked. Candidates
with evidence, never auto-applied. The population rule -- tracking or
using -- is on the plan, not hidden.

The commit is a queued run (kind curate_commit). Each step -- publish
the asset and link its survey report, copy Confidentiality/Criticality/
Retention from enrichment onto it with the author as steward, publish
the confirmed sub-resources with their ancestor folders, components
skipped because they materialize on acceptance -- writes its outcome to
an append-only resource_curation record, so the screen shows the act
while it runs and after, and a failure elsewhere is a failed step, not a
lost act. Signed-in only.

manifest_parse gains a fifth sub-parse: the distribution the manifests
declare -- name, entry points, packages, publish workflow (Repo Handoff
checks 2 and 3, folded into the step that already parses the file).
Curate's first claim becomes a fact rather than an inference.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
Repo Handoff item 6. One screen, three columns, one commit. What it is
(confirmed row by row, a question mark on the Dockerfiles, the
implied/no-contract clause on the endpoints), what's in it and what it's
made of (counts, each opening its members), how it relates, then the
manifest of what gets written -- testimony copied, measurements linked,
unresolved things travel -- and the sentence about reversal. The
population rule is said on the pane and the button gated, not hidden;
anonymous sees why it cannot press. Catalogue -> is a queued run: the
record renders under the button and redraws per step as the poll ticks.

The sub-resource count opened an empty rail: the members fallback read
findings under the analysis id, and they are stored as
repo_sub_resource_survey. Mapped, pinned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE: Curate — review and commit (Repo Handoff item 6)
…, like its siblings'

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE tests: register the distribution sub-parse's exclusive return paths
…urveying, count against the selection

The owner pressed Catalogue -> and reported nothing obvious happening.
The first step re-surveys before it publishes (the existing publish
path), which is minutes on a large repository, and the button and the
record said only 'running'. Both now say surveying first, then
publishing, and the record carries a line naming the running step.

The sub-resource step reported '32 of 31 published': the count included
the ancestor folder NestedFile needs. Counted against the selection now,
with the ancestors named separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
…s can publish at all

#46 shipped a publish regression. The step emits one ClassificationAnnotation
per matched technology, all check_name "technology", and carried no item_key -
so any repo matching two or more technologies produced two identical
qualifiedNames and the publisher refused the WHOLE publish before writing:
Curate's Catalogue, the survey pane's Publish, and resync, all of them. Found
by the /next session on the first live press of Curate on egeria_workspaces_git
(2026-09-12 17:13), not by any test.

One line: item_key=m.technology.name, the same convention api_structure
(language), dependency (ecosystem) and documentation (col_type) already follow
for their per-item annotations.

Why no test caught it: test_annotation_check_names inspects static emission
sites and this is a loop. The new test does what the publisher does - runs the
surveyor for real on a fixture matching three technologies and hands the
annotations to assert_unique_qualified_names. On the shipped code it raises the
exact ValueError seen live; with the fix it passes, and a second test pins that
every per-technology annotation carries a distinct item_key.

A loop-shaped guard in test_annotation_check_names would have found this
statically and is worth adding, but as its own change rather than on a hotfix
that blocks a live test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE Curate: what the first live press taught — the slow step says so, the count is against the selection
RE: dependency_support — one item_key, so a repo with two technologies can publish at all
REVIEW-PROMOTION.md (2026-09-12, read against 4d12173), which sat unread
until this evening because it was written to disk while I looked in the
design project.

'1 advisorie': rstrip('s') strips a character set, not a suffix. A real
singular -- ies to y, es after a sibilant, else drop the s -- on the
server and mirrored in the shell, and the n=1 path is tested now.

A typed name was discarded on the next checkbox because the keep-test
was 'does not start with the display name' and the proposal starts with
it too. Touched is tracked from the input event; a typed name wins until
cleared.

Hand-picking erased the facet. The facet is remembered with the set it
selected and a refinement is described -- 'high, plus 1 added by hand,
less 1 removed by hand' -- on the footer, the proposed name and the
provenance line. Verified in the browser through all five moves.

The run date came from the browser and was never populated off one
path. It is on the members payload from the registry's own bookkeeping,
and the route composes the line from the server's date, not the body's.

Facet group labels were cut to their first word; whole now. Group chips
count the group's own count (the server knows it in full); detail chips
say 'counts are of the N shown' when the list is capped. suggest_to is
a perspective or a user id, not any string. The journal joins with a
middot like everything else here.

House rules: no gold fill on the checkboxes; clickable chip labels in
ink; a tree-shaped list says 'promotion selects leaves'; a single-value
list says 'no facets'; zero groups names the source it looked in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
RE promotion: the review's seven defects and the house rules
@dwolfson
dwolfson merged commit 54ebf04 into odpi:main Sep 13, 2026
3 checks passed
dwolfson added a commit that referenced this pull request Sep 13, 2026
RE: the freshness skip names its price; depth/cost measurement doc
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