@andresmassello/uscha 1.89.0 → 1.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -9
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +221 -18
- package/uscha-kit/.claude/skills/uscha-devloop/uscha_top.py +438 -26
- package/uscha-kit/.claude-plugin/plugin.json +1 -1
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/README.md +2 -6
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/install-uscha.py +5 -0
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +221 -18
- package/uscha-kit/skills/uscha-devloop/uscha_top.py +438 -26
- package/uscha-kit/uscha.config.json +1 -1
- package/uscha-kit/reports/junit/.bench-cases.json +0 -1
- package/uscha-kit/reports/junit/.bench-curate-cases.json +0 -1
- package/uscha-kit/reports/junit/.bootstrap-cases.json +0 -1
- package/uscha-kit/reports/junit/.cleanroom-cases.json +0 -1
- package/uscha-kit/reports/junit/.compile-cases.json +0 -1
- package/uscha-kit/reports/junit/.curation-cases.json +0 -1
- package/uscha-kit/reports/junit/.delta-cases.json +0 -1
- package/uscha-kit/reports/junit/.fa-cases.json +0 -1
- package/uscha-kit/reports/junit/.facts-cases.json +0 -1
- package/uscha-kit/reports/junit/.fastpath-cases.json +0 -1
- package/uscha-kit/reports/junit/.fidelity-cases.json +0 -1
- package/uscha-kit/reports/junit/.goldencov-cases.json +0 -1
- package/uscha-kit/reports/junit/.ir-cases.json +0 -1
- package/uscha-kit/reports/junit/.js-cases.json +0 -1
- package/uscha-kit/reports/junit/.lang-cases.json +0 -1
- package/uscha-kit/reports/junit/.lang3-cases.json +0 -1
- package/uscha-kit/reports/junit/.multi-cases.json +0 -1
- package/uscha-kit/reports/junit/.oracle-cases.json +0 -1
- package/uscha-kit/reports/junit/.origin-cases.json +0 -1
- package/uscha-kit/reports/junit/.r2-cases.json +0 -1
- package/uscha-kit/reports/junit/.rt-cases.json +0 -1
- package/uscha-kit/reports/junit/.sched-cases.json +0 -1
- package/uscha-kit/reports/junit/.specdrift-cases.json +0 -1
- package/uscha-kit/reports/junit/.top-cases.json +0 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ the rest.* Uscha gives a coding agent a spec to build against, a QA loop that co
|
|
|
5
5
|
instead of looping forever, and a deterministic ledger that records what was **measured** —
|
|
6
6
|
never what was claimed.
|
|
7
7
|
|
|
8
|
-
> The
|
|
8
|
+
> The agent executes · the method governs · evidence decides · the human approves.
|
|
9
9
|
|
|
10
10
|
**[uscha.dev](https://uscha.dev)** — the method, the five rules, the skills, the library
|
|
11
11
|
(the diamond thesis, how-it-works diagrams, essay, 2-day dev course, reference, paper).
|
|
@@ -40,7 +40,7 @@ Requires **Python 3.8+** on the machine (the engine is Python stdlib — no pip
|
|
|
40
40
|
runtime dependencies). The npm package is a thin router; the canonical installer is
|
|
41
41
|
`uscha-kit/install-uscha.py`.
|
|
42
42
|
|
|
43
|
-
**Kit v1.
|
|
43
|
+
**Kit v1.91.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
|
|
44
44
|
[changelog](https://github.com/andresmassello/uscha/blob/main/uscha-kit/CHANGELOG.md)
|
|
45
45
|
(the per-release changelogs live in the repo, not in the npm tarball)
|
|
46
46
|
|
|
@@ -82,25 +82,29 @@ automatic tool can perform: a human verdict.
|
|
|
82
82
|
system (= 100% drift)
|
|
83
83
|
|
|
84
84
|
round trip · bench-roundtrip — how much of the asset the reverse organs re-anchor
|
|
85
|
-
from the compiled code: 0.
|
|
85
|
+
from the compiled code: 0.828 measured (12 archetypes) — names AND behaviour
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
**What each arrow is, in the engine (kit 1.
|
|
88
|
+
**What each arrow is, in the engine (kit 1.91.0, 52 subcommands, all measured):**
|
|
89
89
|
|
|
90
90
|
| Leg | Subcommands | What it establishes |
|
|
91
91
|
|---|---|---|
|
|
92
92
|
| Asset → typed graph | `ir-extract`, `ir-render` | the whole package becomes one canonical IR (M2, ADR-015) — deterministic, `UNTYPED` is a measurement not an error |
|
|
93
93
|
| Forward, the compiler | `compile-validate`, `compile-ingest` | any model produces code; the engine validates the output contract and never compiles (M3, ADR-016) |
|
|
94
|
-
| Forward, is it the *same* system? | `bootstrap-oracle`, `bootstrap-variance`, `bench` | a withheld oracle judges blind compilations — **12 archetypes, 9 PASS · 3 PARTIAL**, three models, JS included (M4/M5, ADR-017/018/028/029) |
|
|
94
|
+
| Forward, is it the *same* system? | `bootstrap-oracle`, `bootstrap-variance`, `bench` | a withheld oracle judges blind compilations — **12 archetypes, 9 PASS · 3 PARTIAL**, three Claude-family models (Haiku · Sonnet · Opus — one vendor; cross-vendor not yet measured), JS included (M4/M5, ADR-017/018/028/029) |
|
|
95
95
|
| Reverse, facts | `discover`, `golden-diff` (+ the `/uscha-characterize` skill) | system map + mechanically captured golden; typed candidate observations with evidence class (M1, ADR-013) |
|
|
96
96
|
| Reverse, the human gate | `curate`, `promote`, `curation-check`, `bench-curate` | one verdict per candidate, append-only ledger verified against git; unjudged → `pr-ready` blocked naming it (ADR-009/010, INV-CURATION-01) |
|
|
97
|
-
| Fidelity, honestly | `fidelity`, `roundtrip`, `bench-roundtrip`, `bench-r2` | per-compiler fidelity vector, id-level round trip, recoverability **0.
|
|
97
|
+
| Fidelity, honestly | `fidelity`, `roundtrip`, `bench-roundtrip`, `bench-r2` | per-compiler fidelity vector, id-level round trip, recoverability **0.828**, and the **noise floor** under every variance claim (ADR-014/022/027/030) |
|
|
98
98
|
|
|
99
99
|
**Read the numbers the way the repo does.** 9 of 12 archetypes regenerate to the same system
|
|
100
|
-
under an oracle the compilers never saw — that is the closed loop working. 0.
|
|
100
|
+
under an oracle the compilers never saw — that is the closed loop working. 0.828 is the mean
|
|
101
101
|
*recoverability* of the asset from compiled code counting only static and behavioural
|
|
102
|
-
footing, with the behaviour dimension
|
|
103
|
-
|
|
102
|
+
footing. It read **0.062** until 1.90.0, with the behaviour dimension `UNMEASURED`, because no
|
|
103
|
+
oracle case carried an AC tag — a named absence, not a zero. The 12 bench oracles are now
|
|
104
|
+
curated per case (`ORACLE-TAGS-CURATED.json`, human-authored; payloads and expectations
|
|
105
|
+
untouched), so the dimension is measured and the number says the reverse organs anchor **names
|
|
106
|
+
and behaviour**. Both numbers are published rather than smoothed, and what moved between them
|
|
107
|
+
was the tagging, not the code under test. And `bench-r2` measured
|
|
104
108
|
that same-model reruns differ structurally about as much as different models do (aggregate
|
|
105
109
|
`NOISY`) — so one earlier variance narrative was **retracted**. Every claim above is a subcommand
|
|
106
110
|
you can run; every unmeasured part is labeled. That honesty is the method applied to itself.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andresmassello/uscha",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.91.0",
|
|
4
4
|
"description": "Spec-driven development for LLM coding agents: 9 skills + a stdlib evidence engine. Facts block, guesses advise; the human approves.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Andres Massello",
|
|
@@ -3423,6 +3423,15 @@ def cmd_golden_coverage(args):
|
|
|
3423
3423
|
|
|
3424
3424
|
env = dict(os.environ)
|
|
3425
3425
|
env["COVERAGE_PROCESS_START"] = rc
|
|
3426
|
+
# COVERAGE_FILE is set EXPLICITLY, not merely inherited. coverage.py resolves its
|
|
3427
|
+
# data_file with the environment variable AFTER the rc file, so an inherited
|
|
3428
|
+
# COVERAGE_FILE (the shape the kit's own suite has under USCHA_COVERAGE=1) silently
|
|
3429
|
+
# overrode the isolated data_file above and the capture landed in the caller's shared
|
|
3430
|
+
# file -- the map then read as "covers nothing" and AC-GM-08 went red under coverage
|
|
3431
|
+
# while passing plain. Writing it here pins the child to OUR file whether or not the
|
|
3432
|
+
# caller has one; the parent's own Coverage(data_file=...) already wins over the
|
|
3433
|
+
# environment because constructor arguments are applied last.
|
|
3434
|
+
env["COVERAGE_FILE"] = data_file
|
|
3426
3435
|
env["PYTHONPATH"] = tmp + os.pathsep + env.get("PYTHONPATH", "")
|
|
3427
3436
|
env["PYTHONIOENCODING"] = "utf-8"
|
|
3428
3437
|
r = subprocess.run([sys.executable, harness], cwd=root, env=env,
|
|
@@ -5468,6 +5477,47 @@ def _static_surface_for(cd, unit):
|
|
|
5468
5477
|
return so, []
|
|
5469
5478
|
|
|
5470
5479
|
|
|
5480
|
+
def _judged_env():
|
|
5481
|
+
"""The environment a JUDGED program gets: this process's, minus the hooks that would make
|
|
5482
|
+
a third party instrument it, plus the one setting that keeps it from leaving anything
|
|
5483
|
+
behind in the tree it is judged from.
|
|
5484
|
+
|
|
5485
|
+
The withheld oracle measures a compiled implementation by its exit code and its stdout. A
|
|
5486
|
+
measurement that changes what it measures is a broken measurement, and coverage.py's
|
|
5487
|
+
documented subprocess hook does exactly that: with `COVERAGE_PROCESS_START` in the
|
|
5488
|
+
environment, coverage.py's own `.pth` (`a1_coverage.pth`, shipped in site-packages) starts
|
|
5489
|
+
a full Coverage in EVERY python process, at interpreter start-up, before any of that
|
|
5490
|
+
process's own code runs. The oracle's children are fixture programs that sit outside every
|
|
5491
|
+
`--source` root, so what they record is empty by construction -- pure cost, no measurement.
|
|
5492
|
+
|
|
5493
|
+
That cost is not theoretical (1.90.0). Running this repo's own smoke suite under
|
|
5494
|
+
`USCHA_COVERAGE=1` spawned ~14,000 such children; `coverage combine` reported
|
|
5495
|
+
"Combined 600 files, skipped 13702" -- 96% of them recorded nothing -- and late in the run
|
|
5496
|
+
Windows began refusing to create processes: seven bench archetypes flipped to FAIL because
|
|
5497
|
+
an oracle child could not start, and a py3.8 interpreter came back 3221225794
|
|
5498
|
+
(0xC0000142, STATUS_DLL_INIT_FAILED) with an empty stderr. Nothing was wrong with the
|
|
5499
|
+
engine or the fixtures; the instrument was crushing the machine it measured on.
|
|
5500
|
+
|
|
5501
|
+
Only the two variables that START coverage in a child are dropped. `COVERAGE_FILE` is
|
|
5502
|
+
left alone: it names a data file, it does not turn anything on, and a caller that set it
|
|
5503
|
+
means it.
|
|
5504
|
+
|
|
5505
|
+
`PYTHONDONTWRITEBYTECODE` is SET, and that one is not about coverage (1.90.0, ADR-030
|
|
5506
|
+
amended). A multi-unit compilation imports its sibling module, so running it writes
|
|
5507
|
+
`__pycache__/*.pyc` INTO the fixture directory -- and the round trip's own criterion is
|
|
5508
|
+
that the instrument regenerates nothing under the bench tree (AC-RT-01 snapshots the file
|
|
5509
|
+
list before and after). It only became reachable when the behaviour dimension started
|
|
5510
|
+
running cases: measured on a clean tree, `bench-roundtrip` left three `.pyc` files behind.
|
|
5511
|
+
In the suite the earlier `bench` block had already warmed the cache, so the red would have
|
|
5512
|
+
waited for a fresh CI clone to appear -- an ordering dependency, which is the kind of
|
|
5513
|
+
green worth nothing. Judging a program must not modify what is being judged."""
|
|
5514
|
+
env = dict(os.environ)
|
|
5515
|
+
for key in ("COVERAGE_PROCESS_START", "COVERAGE_PROCESS_CONFIG"):
|
|
5516
|
+
env.pop(key, None)
|
|
5517
|
+
env["PYTHONDONTWRITEBYTECODE"] = "1"
|
|
5518
|
+
return env
|
|
5519
|
+
|
|
5520
|
+
|
|
5471
5521
|
def _impl_interpreter(impl_path):
|
|
5472
5522
|
"""Resolve the interpreter argv prefix for one implementation file by extension (ADR-028):
|
|
5473
5523
|
`.py` runs under this same Python (unchanged); `.js` runs under `node`, resolved from PATH.
|
|
@@ -5508,7 +5558,7 @@ def _run_oracle_case(impl_path, case):
|
|
|
5508
5558
|
# sibling modules by bare name; single-unit impls are unaffected by their cwd
|
|
5509
5559
|
r = subprocess.run(interp + [os.path.abspath(impl_path)], input=stdin,
|
|
5510
5560
|
capture_output=True, text=True, encoding="utf-8", errors="replace",
|
|
5511
|
-
timeout=_BOOTSTRAP_CASE_TIMEOUT,
|
|
5561
|
+
timeout=_BOOTSTRAP_CASE_TIMEOUT, env=_judged_env(),
|
|
5512
5562
|
cwd=os.path.dirname(os.path.abspath(impl_path)) or None)
|
|
5513
5563
|
got, out, err = r.returncode, r.stdout, None
|
|
5514
5564
|
except subprocess.TimeoutExpired:
|
|
@@ -6307,6 +6357,41 @@ def _rt_ids_in(text):
|
|
|
6307
6357
|
return out
|
|
6308
6358
|
|
|
6309
6359
|
|
|
6360
|
+
def _rt_ac_key(cid):
|
|
6361
|
+
"""ONE spelling for comparing an AC id, so the round-trip's footings cannot disagree over
|
|
6362
|
+
punctuation (ADR-030, amended 1.90.0).
|
|
6363
|
+
|
|
6364
|
+
The three footings read ids from three places that spell them differently: an IR node id is
|
|
6365
|
+
written the way a human wrote the acceptance line and keeps its padding (`AC-DD-07`), a
|
|
6366
|
+
source comment or a curated oracle tag may write `AC-DD-7` or `AC_DD_07`, and `_rt_ids_in`
|
|
6367
|
+
only upper-cases what it finds. Comparing those raw was a match that depended on a zero.
|
|
6368
|
+
The normal form is ADR-036's OWN grammar (`_ac_tag_ids`, family + integer, padding and
|
|
6369
|
+
separator dropped) -- reused rather than reimplemented, because a second grammar for the
|
|
6370
|
+
same id is the drift this function exists to prevent. It is applied to BOTH sides of the
|
|
6371
|
+
behaviour comparison and nowhere else: `per_node["id"]`, the static footing and the
|
|
6372
|
+
manifest footing keep the IR's own spelling, so no report changes shape. A non-AC id
|
|
6373
|
+
(INV-*, ADR-*) has no behaviour footing at all and falls through upper-cased."""
|
|
6374
|
+
ids = _ac_tag_ids(str(cid or ""))
|
|
6375
|
+
return ids[0] if ids else str(cid or "").upper().replace("_", "-")
|
|
6376
|
+
|
|
6377
|
+
|
|
6378
|
+
def _rt_case_tags(case):
|
|
6379
|
+
"""Every AC id ONE withheld-oracle case is tagged with, normalised (ADR-030, amended
|
|
6380
|
+
1.90.0): the ids literally referenced in its `name`, plus the ids in its curated `ac` list.
|
|
6381
|
+
|
|
6382
|
+
`ac` is a human-curated field on the case (provenance:
|
|
6383
|
+
`tests/fixtures/diamond-bench/ORACLE-TAGS-CURATED.json`) -- the payload and the expectations
|
|
6384
|
+
are untouched by it, so tagging cannot change what a case MEASURES, only what the id map
|
|
6385
|
+
says it measures. Absence stays absence: a missing key, a null, a non-list, or a list with
|
|
6386
|
+
nothing usable in it yields no tags, and an entry whose cases carry none is still
|
|
6387
|
+
behaviour-UNMEASURED rather than behaviour-zero."""
|
|
6388
|
+
out = set(_rt_ids_in(case.get("name", "")))
|
|
6389
|
+
raw = case.get("ac")
|
|
6390
|
+
if isinstance(raw, (list, tuple)):
|
|
6391
|
+
out |= {t for t in (str(x).strip() for x in raw if isinstance(x, str)) if t}
|
|
6392
|
+
return {_rt_ac_key(t) for t in out}
|
|
6393
|
+
|
|
6394
|
+
|
|
6310
6395
|
def _rt_read_source(cd, unit):
|
|
6311
6396
|
try:
|
|
6312
6397
|
with open(os.path.join(cd, unit.replace("/", os.sep)), encoding="utf-8",
|
|
@@ -6321,8 +6406,9 @@ def _rt_compilation(entry_dir, cd, ir_graph, cases):
|
|
|
6321
6406
|
IR node, whether the mechanical reverse organs find footing for it in the artifact --
|
|
6322
6407
|
(a) static: a source unit's text or a static observation literally references the id;
|
|
6323
6408
|
(b) manifest: the compiler's validated trace manifest maps the node to a unit that exists;
|
|
6324
|
-
(c) behaviour: for AC nodes, at least one withheld-oracle case tagged with the id passes
|
|
6325
|
-
|
|
6409
|
+
(c) behaviour: for AC nodes, at least one withheld-oracle case tagged with the id passes --
|
|
6410
|
+
tagged in the case NAME or in its curated `ac` list (ADR-030 amended 1.90.0), and
|
|
6411
|
+
UNMEASURED when no case carries any tag at all. It regenerates NOTHING: no IR', no spec --
|
|
6326
6412
|
a coverage over the human-authored IR, plus the list of nodes nothing anchors."""
|
|
6327
6413
|
cj = os.path.join(cd, "COMPILATION.json")
|
|
6328
6414
|
try:
|
|
@@ -6347,26 +6433,40 @@ def _rt_compilation(entry_dir, cd, ir_graph, cases):
|
|
|
6347
6433
|
if e.get("unit") in units:
|
|
6348
6434
|
for nid in e.get("implements") or []:
|
|
6349
6435
|
manifest_ids.add(str(nid).upper())
|
|
6350
|
-
# (c) behaviour footing: oracle cases
|
|
6351
|
-
#
|
|
6436
|
+
# (c) behaviour footing: the oracle cases an id is tagged with -- in the case NAME, and
|
|
6437
|
+
# since 1.90.0 in the case's curated `ac` list as well (ADR-030 amended). Before the
|
|
6438
|
+
# curation there was no tag anywhere in this fixture and the whole dimension read
|
|
6439
|
+
# UNMEASURED: a named absence, which is what it was, and it is now measured instead.
|
|
6352
6440
|
tagged = {}
|
|
6353
|
-
for case in cases:
|
|
6354
|
-
for cid in
|
|
6355
|
-
tagged.setdefault(cid, []).append(
|
|
6356
|
-
|
|
6441
|
+
for i, case in enumerate(cases):
|
|
6442
|
+
for cid in _rt_case_tags(case):
|
|
6443
|
+
tagged.setdefault(cid, []).append(i)
|
|
6444
|
+
# MEASURED only when at least one tag names an AC node of THIS entry's IR (1.90.0 review):
|
|
6445
|
+
# a typo, a stray ADR/INV id or a tag from another archetype anchors nothing, and a tag
|
|
6446
|
+
# that anchors nothing must leave the dimension a named absence, never a measured zero.
|
|
6447
|
+
ir_ac_keys = {_rt_ac_key(n) for n in node_ids if str(n).upper().startswith("AC")}
|
|
6448
|
+
behaviour_measured = any(_rt_ac_key(k) in ir_ac_keys for k in tagged)
|
|
6357
6449
|
behaviour_ids = set()
|
|
6358
6450
|
entry_unit = _entry_unit(c.get("source") or [])
|
|
6359
6451
|
impl = os.path.join(cd, entry_unit.replace("/", os.sep)) if entry_unit else None
|
|
6360
6452
|
if behaviour_measured and impl and os.path.isfile(impl):
|
|
6361
|
-
|
|
6362
|
-
|
|
6453
|
+
# one RUN per case, not one per tag: a case tagged with three ids used to be executed
|
|
6454
|
+
# three times, and running a judged program more often than the oracle asked is both
|
|
6455
|
+
# slower and a different experiment
|
|
6456
|
+
verdicts = {}
|
|
6457
|
+
for cid, idxs in tagged.items():
|
|
6458
|
+
for i in idxs:
|
|
6459
|
+
if i not in verdicts:
|
|
6460
|
+
verdicts[i] = bool(_run_oracle_case(impl, cases[i]).get("ok"))
|
|
6461
|
+
if any(verdicts[i] for i in idxs):
|
|
6363
6462
|
behaviour_ids.add(cid)
|
|
6364
6463
|
per_node = []
|
|
6365
6464
|
anchored = 0
|
|
6366
6465
|
for nid in node_ids:
|
|
6367
6466
|
a_s = nid in static_ids
|
|
6368
6467
|
a_m = nid in manifest_ids
|
|
6369
|
-
a_b = (nid in behaviour_ids)
|
|
6468
|
+
a_b = ((_rt_ac_key(nid) in behaviour_ids)
|
|
6469
|
+
if (behaviour_measured and nid.startswith("AC-")) else None)
|
|
6370
6470
|
anchored_any = a_s or a_m or bool(a_b)
|
|
6371
6471
|
# the MEASURED footing excludes the manifest: the manifest is what the compiler
|
|
6372
6472
|
# CLAIMED (and the prompt handed it the ids), so counting it as recovered would be
|
|
@@ -6423,6 +6523,26 @@ def _rt_entry(entry_dir, name):
|
|
|
6423
6523
|
return rec
|
|
6424
6524
|
|
|
6425
6525
|
|
|
6526
|
+
def _rt_behaviour_note(agg):
|
|
6527
|
+
"""The report's closing sentence about the behaviour dimension, DERIVED from the aggregate
|
|
6528
|
+
instead of asserted (1.90.0).
|
|
6529
|
+
|
|
6530
|
+
It used to end 'which today is every entry' -- true when written, false the moment the
|
|
6531
|
+
diamond-bench oracles were curated with per-case `ac` tags, and nothing would have caught
|
|
6532
|
+
it: a generated document is exactly where a hardcoded claim rots unseen. Three honest
|
|
6533
|
+
states, one of them chosen by the number beside it."""
|
|
6534
|
+
measured, total = agg.get("behaviour_measured") or 0, agg.get("measured") or 0
|
|
6535
|
+
if not total or not measured:
|
|
6536
|
+
return ("which today is every entry: the honest state of reverse discovery is that it "
|
|
6537
|
+
"anchors names, not semantics, until oracles carry per-AC tags")
|
|
6538
|
+
if measured == total:
|
|
6539
|
+
return ("which today is no entry: every oracle here carries per-AC tags, so this "
|
|
6540
|
+
"dimension is measured rather than named absent, and what it attributes is "
|
|
6541
|
+
"behaviour and not merely a name")
|
|
6542
|
+
return ("which today is %d of the %d measured entries -- the rest carry per-AC tags and "
|
|
6543
|
+
"are attributed by behaviour" % (total - measured, total))
|
|
6544
|
+
|
|
6545
|
+
|
|
6426
6546
|
def _render_rt_md(recs, agg):
|
|
6427
6547
|
lines = ["<!-- GENERATED by qa_ledger.py bench-roundtrip (ADR-030) -- measured run; do not hand-edit. -->",
|
|
6428
6548
|
"", "# DIAMOND-ROUNDTRIP -- how much of the pinned IR the reverse organs can anchor in each compiled artifact", "",
|
|
@@ -6460,10 +6580,8 @@ def _render_rt_md(recs, agg):
|
|
|
6460
6580
|
"", "*The manifest dimension is what the compiler CLAIMED (validated for shape, "
|
|
6461
6581
|
"not truth) and is excluded from recoverability; the static dimension is what the "
|
|
6462
6582
|
"artifact literally names; the behaviour dimension is what the withheld oracle can "
|
|
6463
|
-
"attribute per AC --
|
|
6464
|
-
"
|
|
6465
|
-
"it anchors names, not semantics, until oracles carry per-AC tags. None of the three "
|
|
6466
|
-
"is a spec.*", ""]
|
|
6583
|
+
"attribute per AC -- UNMEASURED wherever the entry's oracle cases carry no AC tag, "
|
|
6584
|
+
"%s. None of the three is a spec.*" % _rt_behaviour_note(agg), ""]
|
|
6467
6585
|
return "\n".join(lines)
|
|
6468
6586
|
|
|
6469
6587
|
|
|
@@ -8288,9 +8406,17 @@ def _top_clean(text):
|
|
|
8288
8406
|
observation statement, a tool name) -- human and CLI input -- so an ESC or a C0 byte
|
|
8289
8407
|
inside one would be a control sequence the board obeys instead of prints. It dies HERE,
|
|
8290
8408
|
in the engine, and the renderer drops it again on the way out: two cheap guards over one
|
|
8291
|
-
attack surface.
|
|
8409
|
+
attack surface.
|
|
8410
|
+
|
|
8411
|
+
Filtered (1.90.0): C0 and DEL, the C1 range U+0080-U+009F (a terminal decoding the text
|
|
8412
|
+
as latin-1 reads those as CSI/OSC introducers, so they are the same attack in one byte
|
|
8413
|
+
less), and every Unicode format character (category `Cf`) -- U+200B and friends occupy no
|
|
8414
|
+
column but do occupy a codepoint, and U+202E reverses the rest of the line. A character
|
|
8415
|
+
that cannot be seen must not be able to move what is."""
|
|
8292
8416
|
txt = "".join(" " if c in ("\t", "\n", "\r") else c for c in str(text))
|
|
8293
|
-
txt = "".join(c for c in txt
|
|
8417
|
+
txt = "".join(c for c in txt
|
|
8418
|
+
if ord(c) >= 32 and ord(c) != 127 and not 0x80 <= ord(c) <= 0x9F
|
|
8419
|
+
and unicodedata.category(c) != "Cf")
|
|
8294
8420
|
return " ".join(txt.split())
|
|
8295
8421
|
|
|
8296
8422
|
|
|
@@ -8441,6 +8567,78 @@ def _top_events(ledger, limit=TOP_EVENTS_TAIL):
|
|
|
8441
8567
|
return events[:max(0, int(limit))]
|
|
8442
8568
|
|
|
8443
8569
|
|
|
8570
|
+
def _top_repos(ledger):
|
|
8571
|
+
"""The configured repos, name and configured path, in configuration order (phase 2).
|
|
8572
|
+
|
|
8573
|
+
Two things the TUI must not decide for itself now have a source: WHICH repo a rerun runs
|
|
8574
|
+
in and ingests for (ADR-037 picks the first configured one, exactly as `_top_spec_pin`
|
|
8575
|
+
picks the sha it labels), and WHICH repo the `d` pane names when it tells the reader how
|
|
8576
|
+
to produce the spec-drift run that is missing. `path` is the path as CONFIGURED --
|
|
8577
|
+
relative to the ledger, never resolved here: an absolute machine path in the contract is
|
|
8578
|
+
a frozen state nobody else can render (the golden frames are files in a repo)."""
|
|
8579
|
+
out = []
|
|
8580
|
+
for r in ((ledger.get("config", {}) or {}).get("repos") or []):
|
|
8581
|
+
if isinstance(r, dict) and r.get("name"):
|
|
8582
|
+
out.append({"name": _top_clean(r["name"]), "path": _top_clean(r.get("path", "."))})
|
|
8583
|
+
return out
|
|
8584
|
+
|
|
8585
|
+
|
|
8586
|
+
def _top_spec_diff(ledger):
|
|
8587
|
+
"""The advisory spec↔code drift the ledger ALREADY carries, projected for `d` (ADR-037).
|
|
8588
|
+
|
|
8589
|
+
It measures NOTHING: `spec-drift` (ADR-005) is the only command that walks git for this,
|
|
8590
|
+
and `top` is read-only by contract (ADR-032) -- so this reads `ledger["spec_drift"]`, the
|
|
8591
|
+
latest-state record that command leaves behind, and nothing else. **No recorded run ->
|
|
8592
|
+
`null`**, which the TUI renders as "no spec-drift run recorded", never as "no drift":
|
|
8593
|
+
"nobody measured" and "nothing is stale" are different statements and only one of them is
|
|
8594
|
+
ever free (INV-TOP-05).
|
|
8595
|
+
|
|
8596
|
+
Only `SPEC_STALE` rows travel: CLEAN/UNMAPPED/UNTRACKED/NO-CODE are the four ways a doc
|
|
8597
|
+
is NOT drifting, and `docs_total` keeps the denominator visible beside the count so the
|
|
8598
|
+
pane can say `3 of 21`. `code_ref` is ONE of the governed files that outran the doc --
|
|
8599
|
+
the record stores a capped, alphabetically sorted list and no per-file dates, so it is
|
|
8600
|
+
"a newer file", never "the newest one" (under-claim; `newer_files_total` carries the
|
|
8601
|
+
real cardinality)."""
|
|
8602
|
+
rec = ledger.get("spec_drift")
|
|
8603
|
+
if not isinstance(rec, dict):
|
|
8604
|
+
return None
|
|
8605
|
+
results = rec.get("results")
|
|
8606
|
+
results = results if isinstance(results, list) else []
|
|
8607
|
+
docs = [r for r in results if isinstance(r, dict)]
|
|
8608
|
+
stale = []
|
|
8609
|
+
for r in docs:
|
|
8610
|
+
if r.get("verdict") != "SPEC_STALE":
|
|
8611
|
+
continue
|
|
8612
|
+
# `newer_files` is a LIST in the record `spec-drift` writes, but the ledger is JSON on
|
|
8613
|
+
# disk: a hand edit can leave a string there, and a string is iterable -- the old
|
|
8614
|
+
# comprehension would have walked its characters and named `"n"` as the governed file
|
|
8615
|
+
# that outran the doc. A non-list is no evidence, so it yields no code_ref and 0.
|
|
8616
|
+
nf = r.get("newer_files")
|
|
8617
|
+
newer = [f for f in nf if isinstance(f, str)] if isinstance(nf, list) else []
|
|
8618
|
+
lag = r.get("lag_days_actual")
|
|
8619
|
+
total = r.get("newer_files_total")
|
|
8620
|
+
stale.append({
|
|
8621
|
+
"doc": _top_clean(r.get("file") or "?"),
|
|
8622
|
+
"lag_days": lag if isinstance(lag, (int, float)) else None,
|
|
8623
|
+
"code_ref": _top_clean(newer[0]) if newer else None,
|
|
8624
|
+
"newer_files_total": total if isinstance(total, int) else len(newer),
|
|
8625
|
+
"spec_committed_at": (_top_clean(r["spec_committed_at"])
|
|
8626
|
+
if r.get("spec_committed_at") else None),
|
|
8627
|
+
"newest_governed_at": (_top_clean(r["newest_governed_at"])
|
|
8628
|
+
if r.get("newest_governed_at") else None)})
|
|
8629
|
+
# worst lag first, the doc name as the tie-break: deterministic given the record, which
|
|
8630
|
+
# is what lets a golden frame be the oracle for this pane too (ADR-034).
|
|
8631
|
+
stale.sort(key=lambda s: (-(s["lag_days"] or 0), s["doc"]))
|
|
8632
|
+
lag_days = rec.get("max_lag_days")
|
|
8633
|
+
return {"measured_at": _top_clean(rec["at"]) if rec.get("at") else None,
|
|
8634
|
+
"repo": _top_clean(rec["repo"]) if rec.get("repo") else None,
|
|
8635
|
+
"max_lag_days": lag_days if isinstance(lag_days, int) else None,
|
|
8636
|
+
"docs_total": len(docs),
|
|
8637
|
+
"stale": stale,
|
|
8638
|
+
"advisory": True, # ADR-005: this never gates, here or anywhere
|
|
8639
|
+
"source": "spec-drift"}
|
|
8640
|
+
|
|
8641
|
+
|
|
8444
8642
|
def cmd_top(args):
|
|
8445
8643
|
"""`uscha top` — the WHOLE projection of the ledger as one read-only JSON (ADR-032).
|
|
8446
8644
|
|
|
@@ -8573,6 +8771,11 @@ def cmd_top(args):
|
|
|
8573
8771
|
"medians": {"verdict_min": None, "loop_min": _top_loop_median_min(ledger)},
|
|
8574
8772
|
"checks": _top_checks(ledger),
|
|
8575
8773
|
"drift_pct": None, # spec_drift is per-file; an aggregate is ADR-035/3
|
|
8774
|
+
# phase 2 (ADR-037): the repos in configuration order -- the source for the ONE repo
|
|
8775
|
+
# `o` reruns in and `d` names -- and the advisory drift ALREADY recorded by
|
|
8776
|
+
# `spec-drift`. Both are reads of what the ledger holds; `top` still runs nothing.
|
|
8777
|
+
"repos": _top_repos(ledger),
|
|
8778
|
+
"spec_diff": _top_spec_diff(ledger),
|
|
8576
8779
|
# the ONLY real series is the readiness SCORE history; an obligation-count burn-up
|
|
8577
8780
|
# needs new persistence (ADR-035/2), so `kind` is emitted for the TUI to label it a
|
|
8578
8781
|
# score trend and never as a count of closed obligations.
|