@andresmassello/uscha 1.88.0 → 1.90.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 +11 -7
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +211 -38
- package/uscha-kit/.claude/skills/uscha-devloop/uscha_top.py +501 -24
- package/uscha-kit/.claude-plugin/plugin.json +1 -1
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/README.md +1 -1
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/install-uscha.py +8 -1
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +211 -38
- package/uscha-kit/skills/uscha-devloop/uscha_top.py +501 -24
- 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
|
@@ -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.90.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,10 +82,10 @@ 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.90.0, 52 subcommands, all measured):**
|
|
89
89
|
|
|
90
90
|
| Leg | Subcommands | What it establishes |
|
|
91
91
|
|---|---|---|
|
|
@@ -94,13 +94,17 @@ automatic tool can perform: a human verdict.
|
|
|
94
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) |
|
|
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.90.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
|
|
|
@@ -8234,6 +8352,7 @@ def _top_ac_key(cid):
|
|
|
8234
8352
|
|
|
8235
8353
|
TOP_EVENTS_TAIL = 8 # how many steps the feed carries; the TUI shows what fits
|
|
8236
8354
|
TOP_EVENT_WIDTH = 72 # one feed line, short enough to survive the 80-column floor
|
|
8355
|
+
TOP_OBS_TITLE_WIDTH = 72 # the verdict queue's one-line label; the CLAIM is never capped
|
|
8237
8356
|
|
|
8238
8357
|
# kind -> level, the FIXED map ADR-032 (amended 1.88.0, M2) requires. `level` and `text` do
|
|
8239
8358
|
# not exist in `ledger["steps"]`; they are derived here, once, so the TUI renders a feed it
|
|
@@ -8281,20 +8400,74 @@ def _top_key(value):
|
|
|
8281
8400
|
return repr(value)
|
|
8282
8401
|
|
|
8283
8402
|
|
|
8284
|
-
def
|
|
8285
|
-
"""One
|
|
8403
|
+
def _top_clean(text):
|
|
8404
|
+
"""One state-supplied string with every control character gone and its whitespace
|
|
8405
|
+
collapsed. The contract's free text is ledger prose (an escalation reason, an
|
|
8406
|
+
observation statement, a tool name) -- human and CLI input -- so an ESC or a C0 byte
|
|
8407
|
+
inside one would be a control sequence the board obeys instead of prints. It dies HERE,
|
|
8408
|
+
in the engine, and the renderer drops it again on the way out: two cheap guards over one
|
|
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."""
|
|
8416
|
+
txt = "".join(" " if c in ("\t", "\n", "\r") else c for c in str(text))
|
|
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")
|
|
8420
|
+
return " ".join(txt.split())
|
|
8421
|
+
|
|
8286
8422
|
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
return
|
|
8423
|
+
def _top_cap(text, width):
|
|
8424
|
+
"""A capped string that SAYS it was cut. Never used on a claim the human has to judge --
|
|
8425
|
+
only on labels (a feed line, an observation title), whose full text the contract carries
|
|
8426
|
+
somewhere else (M3: `candidate[]` holds the whole claim, `title` only its head)."""
|
|
8427
|
+
return text if len(text) <= width else text[:width - 1] + "…"
|
|
8428
|
+
|
|
8429
|
+
|
|
8430
|
+
def _top_event_text(*parts):
|
|
8431
|
+
"""One feed line: free text of the contract that reaches a terminal, sanitized and
|
|
8432
|
+
capped to survive the 80-column floor."""
|
|
8433
|
+
return _top_cap(_top_clean(" · ".join(str(p) for p in parts
|
|
8434
|
+
if p not in (None, "", "?"))), TOP_EVENT_WIDTH)
|
|
8435
|
+
|
|
8436
|
+
|
|
8437
|
+
def _top_obs_view(o, repo):
|
|
8438
|
+
"""ONE uncurated observation as the verdicts queue reads it (ADR-032, amended 1.89.0 for
|
|
8439
|
+
M3). The delta record is the only source: `type`, `statement`, `provenance.files`,
|
|
8440
|
+
`evidence_class` -- nothing here is inferred, and a member the delta does not carry comes
|
|
8441
|
+
out empty rather than guessed.
|
|
8442
|
+
|
|
8443
|
+
- `repo` is the delta's OWN repo, and it is in the contract because the write path needs
|
|
8444
|
+
it: `curate` takes `--repo`, so a queue without it would force the TUI to pick one --
|
|
8445
|
+
the one derivation the TUI is never allowed to make (ADR-033).
|
|
8446
|
+
- `title` is the statement's HEAD, capped and marked with an ellipsis when cut. It is a
|
|
8447
|
+
label for the list line only. The claim the human judges is in `candidate[]` in full:
|
|
8448
|
+
a verdict recorded on half a sentence is the failure this split exists to prevent.
|
|
8449
|
+
- a delta is JSON on disk, so `provenance` can arrive as a list from a hand edit -- the
|
|
8450
|
+
read-only readout degrades that observation to no evidence line, it does not raise."""
|
|
8451
|
+
prov = o.get("provenance") if isinstance(o.get("provenance"), dict) else {}
|
|
8452
|
+
raw = prov.get("files") if isinstance(prov.get("files"), list) else []
|
|
8453
|
+
files = [f for f in (_top_clean(x) for x in raw) if f]
|
|
8454
|
+
statement = _top_clean(o.get("statement"))
|
|
8455
|
+
otype = _top_clean(o.get("type"))
|
|
8456
|
+
site = files[0] if files else ""
|
|
8457
|
+
candidate = ["type: %s%s" % (otype or "?", (" · site: %s" % site) if site else "")]
|
|
8458
|
+
if statement:
|
|
8459
|
+
candidate.append("claim: " + statement) # WHOLE, never capped
|
|
8460
|
+
evidence = list(files)
|
|
8461
|
+
cls, tool = _top_clean(o.get("evidence_class")), _top_clean(prov.get("tool"))
|
|
8462
|
+
if cls or tool:
|
|
8463
|
+
evidence.append("evidence_class: %s%s" % (cls or "?",
|
|
8464
|
+
(" · tool: %s" % tool) if tool else ""))
|
|
8465
|
+
return {"id": o.get("id"), "ac": o.get("canonical_match"), "repo": repo,
|
|
8466
|
+
"title": _top_cap(statement, TOP_OBS_TITLE_WIDTH) if statement else None,
|
|
8467
|
+
"candidate": candidate, "evidence": evidence,
|
|
8468
|
+
# no per-observation first-seen timestamp exists (ADR-032/035): the queue is
|
|
8469
|
+
# ordered by the criterion it anchors, not by an age nobody recorded.
|
|
8470
|
+
"age_hours": None}
|
|
8298
8471
|
|
|
8299
8472
|
|
|
8300
8473
|
def _top_events(ledger, limit=TOP_EVENTS_TAIL):
|
|
@@ -8444,15 +8617,15 @@ def cmd_top(args):
|
|
|
8444
8617
|
cid = o.get("canonical_match")
|
|
8445
8618
|
if cid and cid not in quarantine:
|
|
8446
8619
|
quarantine[cid] = o["id"]
|
|
8447
|
-
observations.append(
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8620
|
+
observations.append(_top_obs_view(o, rname))
|
|
8621
|
+
# the queue's order: the criterion each observation anchors first (by the same key the
|
|
8622
|
+
# board orders obligations with), the unanchored ones after, and the content-addressed
|
|
8623
|
+
# id as the tie-break. Deterministic given the delta -- which is what lets a golden frame
|
|
8624
|
+
# be the oracle for the verdicts pane. AGE-descending, which the SPEC drafted, is not
|
|
8625
|
+
# derivable: every age_hours is null (no first-seen timestamp), and ordering by a value
|
|
8626
|
+
# that does not exist would be the fabrication INV-TOP-05 forbids.
|
|
8627
|
+
observations.sort(key=lambda o: ((_top_ac_key(o["ac"]) if o.get("ac") else (3, "", 0)),
|
|
8628
|
+
o.get("id") or ""))
|
|
8456
8629
|
|
|
8457
8630
|
# obligations: one row per DISTINCT tagged criterion of the acceptance file. kind is
|
|
8458
8631
|
# "AC" for all of them -- there is no per-INV ledger in the general path (the mirador's
|