@andresmassello/uscha 1.89.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 +144 -18
- package/uscha-kit/.claude/skills/uscha-devloop/uscha_top.py +90 -19
- 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/skills/uscha-devloop/qa_ledger.py +144 -18
- package/uscha-kit/skills/uscha-devloop/uscha_top.py +90 -19
- 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
|
|
|
@@ -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
|
|
|
@@ -30,6 +30,7 @@ import shutil
|
|
|
30
30
|
import subprocess
|
|
31
31
|
import sys
|
|
32
32
|
import time
|
|
33
|
+
import unicodedata
|
|
33
34
|
|
|
34
35
|
DEFAULT_LEDGER = "QA-LEDGER.json"
|
|
35
36
|
FALLBACK_SIZE = (100, 32)
|
|
@@ -105,21 +106,71 @@ ACTIONS = {
|
|
|
105
106
|
# --------------------------------------------------------------------------- #
|
|
106
107
|
# pure rendering #
|
|
107
108
|
# --------------------------------------------------------------------------- #
|
|
109
|
+
def _dw(text):
|
|
110
|
+
"""Display width in TERMINAL COLUMNS, not codepoints.
|
|
111
|
+
|
|
112
|
+
`len()` counts codepoints, and the frame's whole contract is columns: a CJK project name
|
|
113
|
+
or a full-width event text takes two columns per codepoint, so a line `len()` called
|
|
114
|
+
exactly `cols` wide draws twice that and the frame every golden pins stops being a frame
|
|
115
|
+
(1.86.1 fresh review, LOW, deferred until a fixture existed -- `state-wide.json` is it).
|
|
116
|
+
|
|
117
|
+
Three classes, and no font metric anywhere: East Asian Wide and Fullwidth cost 2, a
|
|
118
|
+
combining mark costs 0 (it draws on the previous cell), everything else costs 1. East
|
|
119
|
+
Asian *Ambiguous* deliberately counts 1 -- that is the class the renderer's own glyphs
|
|
120
|
+
fall into (`…`, `·`, `─`, `│`, `▁`, `—`), so on an ASCII state `_dw` is `len` and every
|
|
121
|
+
frame captured before this function existed stays byte-identical."""
|
|
122
|
+
width = 0
|
|
123
|
+
for ch in str(text):
|
|
124
|
+
if unicodedata.combining(ch):
|
|
125
|
+
continue
|
|
126
|
+
width += 2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1
|
|
127
|
+
return width
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _cut(text, width):
|
|
131
|
+
"""The longest PREFIX of `text` that fits in `width` columns, whole characters only.
|
|
132
|
+
|
|
133
|
+
A wide character is never split: half a glyph is not half a column, it is a cell the
|
|
134
|
+
terminal fills however it likes and a frame nobody can snapshot."""
|
|
135
|
+
if width <= 0:
|
|
136
|
+
return ""
|
|
137
|
+
out, used = [], 0
|
|
138
|
+
for ch in str(text):
|
|
139
|
+
w = 0 if unicodedata.combining(ch) else (
|
|
140
|
+
2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1)
|
|
141
|
+
if used + w > width:
|
|
142
|
+
break
|
|
143
|
+
out.append(ch)
|
|
144
|
+
used += w
|
|
145
|
+
return "".join(out)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _pad(text, width):
|
|
149
|
+
"""`str.ljust` measured in columns. Padding by codepoints puts a wide cell's second
|
|
150
|
+
column inside the next field and every column after it walks."""
|
|
151
|
+
return str(text) + " " * max(0, width - _dw(text))
|
|
152
|
+
|
|
153
|
+
|
|
108
154
|
def _fit(text, cols):
|
|
109
155
|
"""One line, never wider than the terminal. Wrapping would break the frame's row
|
|
110
|
-
accounting, so an over-long line is cut and marked.
|
|
156
|
+
accounting, so an over-long line is cut and marked.
|
|
157
|
+
|
|
158
|
+
Measured and cut in COLUMNS (`_dw`/`_cut`): cutting by codepoints was the bug. Note the
|
|
159
|
+
cut may leave one column short rather than land exactly on `cols - 1` -- when the
|
|
160
|
+
character at the boundary is wide it is dropped whole, and a frame one column narrow is
|
|
161
|
+
correct where a frame one column wide is not."""
|
|
111
162
|
if cols <= 0:
|
|
112
163
|
return ""
|
|
113
|
-
if
|
|
164
|
+
if _dw(text) <= cols:
|
|
114
165
|
return text
|
|
115
|
-
return text
|
|
166
|
+
return _cut(text, cols - 1) + "…" if cols > 1 else _cut(text, cols)
|
|
116
167
|
|
|
117
168
|
|
|
118
169
|
def _spread(left, right, cols):
|
|
119
170
|
"""left ... right on one line, right-aligned, degrading to just `left` when tight."""
|
|
120
|
-
if
|
|
171
|
+
if _dw(left) + _dw(right) + 1 > cols:
|
|
121
172
|
return _fit(left, cols)
|
|
122
|
-
return left + " " * (cols -
|
|
173
|
+
return left + " " * (cols - _dw(left) - _dw(right)) + right
|
|
123
174
|
|
|
124
175
|
|
|
125
176
|
def _num(value):
|
|
@@ -151,7 +202,7 @@ def _burnup_line(burnup, cols):
|
|
|
151
202
|
note = " (readiness score, not closed obligations)"
|
|
152
203
|
if not points:
|
|
153
204
|
return label + DASH + " (no `readiness --record` history yet)"
|
|
154
|
-
room = max(4, min(BURNUP_MAX, cols -
|
|
205
|
+
room = max(4, min(BURNUP_MAX, cols - _dw(label) - _dw(note)))
|
|
155
206
|
bars = "".join(BLOCKS[min(len(BLOCKS) - 1, max(0, int(p) * len(BLOCKS) // 101))]
|
|
156
207
|
for p in points[-room:])
|
|
157
208
|
return label + bars + note
|
|
@@ -180,10 +231,13 @@ def _cases_text(ob):
|
|
|
180
231
|
|
|
181
232
|
|
|
182
233
|
def _row(ob, selected):
|
|
234
|
+
# the three left columns are cut and padded in COLUMNS: an id or state carrying wide
|
|
235
|
+
# characters used to eat its neighbour's field and walk every column after it.
|
|
183
236
|
gutter = "> " if selected else " "
|
|
184
|
-
return "%s
|
|
185
|
-
gutter, _safe(ob.get("id") or "?")
|
|
186
|
-
_safe(ob.get("
|
|
237
|
+
return "%s%s%s%s%7s%5s %s" % (
|
|
238
|
+
gutter, _pad(_cut(_safe(ob.get("id") or "?"), 8), 8),
|
|
239
|
+
_pad(_cut(_safe(ob.get("gate") or DASH), 8), 9),
|
|
240
|
+
_pad(_cut(_safe(ob.get("state") or "?"), 14), 15), _cases_text(ob),
|
|
187
241
|
_num(ob.get("age_hours")), ACTIONS.get(ob.get("state"), DASH))
|
|
188
242
|
|
|
189
243
|
|
|
@@ -191,8 +245,22 @@ def _safe(text):
|
|
|
191
245
|
"""No control character reaches the terminal through the feed. The engine already
|
|
192
246
|
strips them where the text is derived (`_top_event_text`); this is the second guard on
|
|
193
247
|
the same surface, because the renderer also accepts a frozen state file a human wrote,
|
|
194
|
-
and one ESC in it would be a control sequence the board obeys instead of prints.
|
|
195
|
-
|
|
248
|
+
and one ESC in it would be a control sequence the board obeys instead of prints.
|
|
249
|
+
|
|
250
|
+
Dropped (1.90.0), matching the engine's `_top_clean` exactly: C0 and DEL, the C1 range
|
|
251
|
+
U+0080-U+009F (a terminal reading the stream as latin-1 takes those for CSI/OSC), and
|
|
252
|
+
every Unicode format character (category `Cf`) -- U+200B costs a codepoint and no column,
|
|
253
|
+
U+202E reverses everything after it. A character that cannot be seen must not be able to
|
|
254
|
+
move what is."""
|
|
255
|
+
out = []
|
|
256
|
+
for ch in str(text or ""):
|
|
257
|
+
code = ord(ch)
|
|
258
|
+
if code < 32 or code == 127 or 0x80 <= code <= 0x9F:
|
|
259
|
+
continue
|
|
260
|
+
if unicodedata.category(ch) == "Cf":
|
|
261
|
+
continue
|
|
262
|
+
out.append(ch)
|
|
263
|
+
return "".join(out)
|
|
196
264
|
|
|
197
265
|
|
|
198
266
|
def _feed_line(ev, cols, plain):
|
|
@@ -355,14 +423,17 @@ def _wrap(text, width):
|
|
|
355
423
|
for word in _safe(text).split():
|
|
356
424
|
if not line:
|
|
357
425
|
line = word
|
|
358
|
-
elif
|
|
426
|
+
elif _dw(line) + 1 + _dw(word) <= width:
|
|
359
427
|
line += " " + word
|
|
360
428
|
else:
|
|
361
429
|
out.append(line)
|
|
362
430
|
line = word
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
431
|
+
# the hard split is measured in columns too, and `_cut` never breaks a wide
|
|
432
|
+
# character in half -- so a wide token continues on the next line, whole.
|
|
433
|
+
while _dw(line) > width:
|
|
434
|
+
head = _cut(line, width)
|
|
435
|
+
out.append(head)
|
|
436
|
+
line = line[len(head):]
|
|
366
437
|
if line:
|
|
367
438
|
out.append(line)
|
|
368
439
|
return out or [""]
|
|
@@ -374,10 +445,10 @@ def _obs_row(i, ob, selected, cols):
|
|
|
374
445
|
gutter = "> " if selected else " "
|
|
375
446
|
idx = "[%d]" % (i + 1) if i < VERDICT_LIST_MAX else " "
|
|
376
447
|
tail = " %s %s %s pending" % (MID, _safe(ob.get("ac")) or ("AC " + DASH), MID)
|
|
377
|
-
head = "%s%-4s
|
|
378
|
-
room = max(4, cols -
|
|
448
|
+
head = "%s%-4s%s" % (gutter, idx, _pad(_cut(_safe(ob.get("id")), 18), 18))
|
|
449
|
+
room = max(4, cols - _dw(head) - _dw(tail))
|
|
379
450
|
title = _fit(_safe(ob.get("title")) or DASH, room)
|
|
380
|
-
return head + title
|
|
451
|
+
return head + _pad(title, room) + tail
|
|
381
452
|
|
|
382
453
|
|
|
383
454
|
def _column_widths(cols):
|
|
@@ -412,7 +483,7 @@ def _pane(ob, cols, height):
|
|
|
412
483
|
pad = max(len(left), len(right))
|
|
413
484
|
left += [""] * (pad - len(left))
|
|
414
485
|
right += [""] * (pad - len(right))
|
|
415
|
-
body = head + [(" %s │ %s" % (l
|
|
486
|
+
body = head + [(" %s │ %s" % (_pad(l, lw), r)).rstrip()
|
|
416
487
|
for l, r in zip(left, right)]
|
|
417
488
|
else:
|
|
418
489
|
body = (head + [" CANDIDATE"] + [" " + ln for ln in block("candidate", cols - 2)]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "uscha",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.90.0",
|
|
5
5
|
"displayName": "Uscha",
|
|
6
6
|
"description": "Spec-driven development for LLM coding agents: 9 skills (discovery, adr-refine, reverse-discovery, characterize, devloop, sysdoc, rubric, mirador, status) + a stdlib measurement engine (qa_ledger.py, 52 subcommands + universal installer + npm/npx router). Facts block, guesses advise; the human approves.",
|
|
7
7
|
"author": {
|
package/uscha-kit/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# uscha-kit
|
|
2
2
|
|
|
3
|
-
**Kit version:** v1.
|
|
3
|
+
**Kit version:** v1.90.0 <!-- uscha:version --> · **[uscha.dev](https://uscha.dev)**
|
|
4
4
|
|
|
5
5
|
Spec-driven orchestrator + multi-repo QA for Claude Code, with a deterministic ledger.
|
|
6
6
|
**Nine skills** (`uscha-discovery`, `uscha-adr-refine`, `uscha-devloop`, `uscha-sysdoc`, `uscha-reverse-discovery`,
|
package/uscha-kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
uscha-kit 1.
|
|
1
|
+
uscha-kit 1.90.0
|
|
@@ -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
|
|
|
@@ -30,6 +30,7 @@ import shutil
|
|
|
30
30
|
import subprocess
|
|
31
31
|
import sys
|
|
32
32
|
import time
|
|
33
|
+
import unicodedata
|
|
33
34
|
|
|
34
35
|
DEFAULT_LEDGER = "QA-LEDGER.json"
|
|
35
36
|
FALLBACK_SIZE = (100, 32)
|
|
@@ -105,21 +106,71 @@ ACTIONS = {
|
|
|
105
106
|
# --------------------------------------------------------------------------- #
|
|
106
107
|
# pure rendering #
|
|
107
108
|
# --------------------------------------------------------------------------- #
|
|
109
|
+
def _dw(text):
|
|
110
|
+
"""Display width in TERMINAL COLUMNS, not codepoints.
|
|
111
|
+
|
|
112
|
+
`len()` counts codepoints, and the frame's whole contract is columns: a CJK project name
|
|
113
|
+
or a full-width event text takes two columns per codepoint, so a line `len()` called
|
|
114
|
+
exactly `cols` wide draws twice that and the frame every golden pins stops being a frame
|
|
115
|
+
(1.86.1 fresh review, LOW, deferred until a fixture existed -- `state-wide.json` is it).
|
|
116
|
+
|
|
117
|
+
Three classes, and no font metric anywhere: East Asian Wide and Fullwidth cost 2, a
|
|
118
|
+
combining mark costs 0 (it draws on the previous cell), everything else costs 1. East
|
|
119
|
+
Asian *Ambiguous* deliberately counts 1 -- that is the class the renderer's own glyphs
|
|
120
|
+
fall into (`…`, `·`, `─`, `│`, `▁`, `—`), so on an ASCII state `_dw` is `len` and every
|
|
121
|
+
frame captured before this function existed stays byte-identical."""
|
|
122
|
+
width = 0
|
|
123
|
+
for ch in str(text):
|
|
124
|
+
if unicodedata.combining(ch):
|
|
125
|
+
continue
|
|
126
|
+
width += 2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1
|
|
127
|
+
return width
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _cut(text, width):
|
|
131
|
+
"""The longest PREFIX of `text` that fits in `width` columns, whole characters only.
|
|
132
|
+
|
|
133
|
+
A wide character is never split: half a glyph is not half a column, it is a cell the
|
|
134
|
+
terminal fills however it likes and a frame nobody can snapshot."""
|
|
135
|
+
if width <= 0:
|
|
136
|
+
return ""
|
|
137
|
+
out, used = [], 0
|
|
138
|
+
for ch in str(text):
|
|
139
|
+
w = 0 if unicodedata.combining(ch) else (
|
|
140
|
+
2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1)
|
|
141
|
+
if used + w > width:
|
|
142
|
+
break
|
|
143
|
+
out.append(ch)
|
|
144
|
+
used += w
|
|
145
|
+
return "".join(out)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _pad(text, width):
|
|
149
|
+
"""`str.ljust` measured in columns. Padding by codepoints puts a wide cell's second
|
|
150
|
+
column inside the next field and every column after it walks."""
|
|
151
|
+
return str(text) + " " * max(0, width - _dw(text))
|
|
152
|
+
|
|
153
|
+
|
|
108
154
|
def _fit(text, cols):
|
|
109
155
|
"""One line, never wider than the terminal. Wrapping would break the frame's row
|
|
110
|
-
accounting, so an over-long line is cut and marked.
|
|
156
|
+
accounting, so an over-long line is cut and marked.
|
|
157
|
+
|
|
158
|
+
Measured and cut in COLUMNS (`_dw`/`_cut`): cutting by codepoints was the bug. Note the
|
|
159
|
+
cut may leave one column short rather than land exactly on `cols - 1` -- when the
|
|
160
|
+
character at the boundary is wide it is dropped whole, and a frame one column narrow is
|
|
161
|
+
correct where a frame one column wide is not."""
|
|
111
162
|
if cols <= 0:
|
|
112
163
|
return ""
|
|
113
|
-
if
|
|
164
|
+
if _dw(text) <= cols:
|
|
114
165
|
return text
|
|
115
|
-
return text
|
|
166
|
+
return _cut(text, cols - 1) + "…" if cols > 1 else _cut(text, cols)
|
|
116
167
|
|
|
117
168
|
|
|
118
169
|
def _spread(left, right, cols):
|
|
119
170
|
"""left ... right on one line, right-aligned, degrading to just `left` when tight."""
|
|
120
|
-
if
|
|
171
|
+
if _dw(left) + _dw(right) + 1 > cols:
|
|
121
172
|
return _fit(left, cols)
|
|
122
|
-
return left + " " * (cols -
|
|
173
|
+
return left + " " * (cols - _dw(left) - _dw(right)) + right
|
|
123
174
|
|
|
124
175
|
|
|
125
176
|
def _num(value):
|
|
@@ -151,7 +202,7 @@ def _burnup_line(burnup, cols):
|
|
|
151
202
|
note = " (readiness score, not closed obligations)"
|
|
152
203
|
if not points:
|
|
153
204
|
return label + DASH + " (no `readiness --record` history yet)"
|
|
154
|
-
room = max(4, min(BURNUP_MAX, cols -
|
|
205
|
+
room = max(4, min(BURNUP_MAX, cols - _dw(label) - _dw(note)))
|
|
155
206
|
bars = "".join(BLOCKS[min(len(BLOCKS) - 1, max(0, int(p) * len(BLOCKS) // 101))]
|
|
156
207
|
for p in points[-room:])
|
|
157
208
|
return label + bars + note
|
|
@@ -180,10 +231,13 @@ def _cases_text(ob):
|
|
|
180
231
|
|
|
181
232
|
|
|
182
233
|
def _row(ob, selected):
|
|
234
|
+
# the three left columns are cut and padded in COLUMNS: an id or state carrying wide
|
|
235
|
+
# characters used to eat its neighbour's field and walk every column after it.
|
|
183
236
|
gutter = "> " if selected else " "
|
|
184
|
-
return "%s
|
|
185
|
-
gutter, _safe(ob.get("id") or "?")
|
|
186
|
-
_safe(ob.get("
|
|
237
|
+
return "%s%s%s%s%7s%5s %s" % (
|
|
238
|
+
gutter, _pad(_cut(_safe(ob.get("id") or "?"), 8), 8),
|
|
239
|
+
_pad(_cut(_safe(ob.get("gate") or DASH), 8), 9),
|
|
240
|
+
_pad(_cut(_safe(ob.get("state") or "?"), 14), 15), _cases_text(ob),
|
|
187
241
|
_num(ob.get("age_hours")), ACTIONS.get(ob.get("state"), DASH))
|
|
188
242
|
|
|
189
243
|
|
|
@@ -191,8 +245,22 @@ def _safe(text):
|
|
|
191
245
|
"""No control character reaches the terminal through the feed. The engine already
|
|
192
246
|
strips them where the text is derived (`_top_event_text`); this is the second guard on
|
|
193
247
|
the same surface, because the renderer also accepts a frozen state file a human wrote,
|
|
194
|
-
and one ESC in it would be a control sequence the board obeys instead of prints.
|
|
195
|
-
|
|
248
|
+
and one ESC in it would be a control sequence the board obeys instead of prints.
|
|
249
|
+
|
|
250
|
+
Dropped (1.90.0), matching the engine's `_top_clean` exactly: C0 and DEL, the C1 range
|
|
251
|
+
U+0080-U+009F (a terminal reading the stream as latin-1 takes those for CSI/OSC), and
|
|
252
|
+
every Unicode format character (category `Cf`) -- U+200B costs a codepoint and no column,
|
|
253
|
+
U+202E reverses everything after it. A character that cannot be seen must not be able to
|
|
254
|
+
move what is."""
|
|
255
|
+
out = []
|
|
256
|
+
for ch in str(text or ""):
|
|
257
|
+
code = ord(ch)
|
|
258
|
+
if code < 32 or code == 127 or 0x80 <= code <= 0x9F:
|
|
259
|
+
continue
|
|
260
|
+
if unicodedata.category(ch) == "Cf":
|
|
261
|
+
continue
|
|
262
|
+
out.append(ch)
|
|
263
|
+
return "".join(out)
|
|
196
264
|
|
|
197
265
|
|
|
198
266
|
def _feed_line(ev, cols, plain):
|
|
@@ -355,14 +423,17 @@ def _wrap(text, width):
|
|
|
355
423
|
for word in _safe(text).split():
|
|
356
424
|
if not line:
|
|
357
425
|
line = word
|
|
358
|
-
elif
|
|
426
|
+
elif _dw(line) + 1 + _dw(word) <= width:
|
|
359
427
|
line += " " + word
|
|
360
428
|
else:
|
|
361
429
|
out.append(line)
|
|
362
430
|
line = word
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
431
|
+
# the hard split is measured in columns too, and `_cut` never breaks a wide
|
|
432
|
+
# character in half -- so a wide token continues on the next line, whole.
|
|
433
|
+
while _dw(line) > width:
|
|
434
|
+
head = _cut(line, width)
|
|
435
|
+
out.append(head)
|
|
436
|
+
line = line[len(head):]
|
|
366
437
|
if line:
|
|
367
438
|
out.append(line)
|
|
368
439
|
return out or [""]
|
|
@@ -374,10 +445,10 @@ def _obs_row(i, ob, selected, cols):
|
|
|
374
445
|
gutter = "> " if selected else " "
|
|
375
446
|
idx = "[%d]" % (i + 1) if i < VERDICT_LIST_MAX else " "
|
|
376
447
|
tail = " %s %s %s pending" % (MID, _safe(ob.get("ac")) or ("AC " + DASH), MID)
|
|
377
|
-
head = "%s%-4s
|
|
378
|
-
room = max(4, cols -
|
|
448
|
+
head = "%s%-4s%s" % (gutter, idx, _pad(_cut(_safe(ob.get("id")), 18), 18))
|
|
449
|
+
room = max(4, cols - _dw(head) - _dw(tail))
|
|
379
450
|
title = _fit(_safe(ob.get("title")) or DASH, room)
|
|
380
|
-
return head + title
|
|
451
|
+
return head + _pad(title, room) + tail
|
|
381
452
|
|
|
382
453
|
|
|
383
454
|
def _column_widths(cols):
|
|
@@ -412,7 +483,7 @@ def _pane(ob, cols, height):
|
|
|
412
483
|
pad = max(len(left), len(right))
|
|
413
484
|
left += [""] * (pad - len(left))
|
|
414
485
|
right += [""] * (pad - len(right))
|
|
415
|
-
body = head + [(" %s │ %s" % (l
|
|
486
|
+
body = head + [(" %s │ %s" % (_pad(l, lw), r)).rstrip()
|
|
416
487
|
for l, r in zip(left, right)]
|
|
417
488
|
else:
|
|
418
489
|
body = (head + [" CANDIDATE"] + [" " + ln for ln in block("candidate", cols - 2)]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-DB-01": true, "AC-DB-02": true, "AC-DB-03": true, "AC-DB-05": true, "AC-DB-04": true, "AC-DB-06": true, "AC-BG-01": true, "AC-BG-02": true, "AC-BG-03": true, "AC-BG-04": true, "AC-BG-05": true, "AC-FC-01": true, "AC-FC-02": true, "AC-FC-03": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-BC-04": true, "AC-BC-01": true, "AC-BC-02": true, "AC-BC-03": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-BS-01": true, "AC-BS-02": true, "AC-BS-03": true, "AC-BS-04": true, "AC-BS-05": true, "AC-BS-06": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-CR-06": true, "AC-CR-02": true, "AC-CR-04": true, "AC-CR-05": true, "AC-CR-03": true, "AC-CR-01": true, "AC-CR-07": true, "AC-CR-08": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-CC-01": true, "AC-CC-02": true, "AC-CC-03": true, "AC-CC-04": true, "AC-CC-05": true, "AC-CC-06": true, "AC-CC-07": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-RD-07": true, "AC-RD-01": true, "AC-RD-02": true, "AC-RD-03": true, "AC-RD-06": true, "AC-RD-04": true, "AC-RD-05": true, "AC-RD-13": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-DD-01": true, "AC-DD-02": true, "AC-DD-03": true, "AC-DD-04": true, "AC-DD-05": true, "AC-DD-06": true, "AC-CU-06": true, "AC-CU-01": true, "AC-CU-02": true, "AC-CU-03": true, "AC-CU-04": true, "AC-CU-05": true, "AC-DD-07": true, "review-m1": true, "review-m3": true, "review-h1": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-FA-01": true, "AC-FA-02": true, "AC-FA-03": null, "AC-FA-04": true, "AC-FA-05": true, "AC-FA-06": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-SF-01": true, "AC-SF-02": true, "AC-SF-03": true, "AC-SF-04": true, "AC-SF-05": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-FP-01": true, "AC-FP-11": true, "AC-FP-07": true, "AC-FP-06": true, "AC-FP-05": true, "AC-FP-02": true, "AC-FP-03": true, "AC-FP-09": true, "AC-FP-10": true, "AC-FP-08": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-FV-01": true, "AC-FV-02": true, "AC-FV-04": true, "AC-FV-05": true, "AC-FV-03": true, "review-h2": true, "AC-FV-06": true, "review-m4": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-GM-01": true, "AC-GM-03": true, "AC-GM-05": true, "AC-GM-04": true, "AC-GM-02": true, "AC-GM-06": true, "AC-GM-07": true, "AC-GM-08": null}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-IR-01": true, "AC-IR-02": true, "AC-IR-03": true, "AC-IR-04": true, "AC-IR-05": true, "AC-IR-06": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-JS-01": true, "AC-JS-02": true, "AC-JS-03": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-CL-01": true, "AC-CL-02": true, "AC-CL-03": true, "AC-CL-04": true, "AC-CL-05": true, "AC-CL-06": true, "AC-CL2-01": true, "AC-CL2-02": true, "AC-CL2-03": true, "AC-CL2-04": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-CL3-01": true, "AC-CL3-02": true, "AC-CL3-03": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-MU-01": true, "AC-MU-02": true, "AC-MU-03": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-RD-08": true, "AC-RD-09": true, "AC-RD-10": true, "AC-RD-11": true, "AC-RD-12": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-EP-01": true, "AC-EP-02": true, "AC-EP-03": true, "AC-EP-05": true, "AC-EP-04": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-R2-01": true, "AC-R2-02": true, "AC-R2-03": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-RT-01": true, "AC-RT-02": true, "AC-RT-03": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-SH-01": true, "AC-SH-02": true, "AC-SH-03": true, "AC-LI-01": true, "AC-LI-02": true, "AC-LI-03": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-SD-01": true, "AC-SD-03": true, "AC-SD-05": true, "AC-SD-02": true, "AC-SD-04": true}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"AC-T-01": true, "AC-T-02": true, "AC-T-03": true, "AC-T-10": true, "AC-T-04": true, "AC-T-05": true, "AC-T-06": true, "AC-T-09": true, "AC-T-24": true, "reg-quarantine-obs-null-on-measured": true, "reg-spec-pin-null-outside-worktree": true, "reg-unreachable-repo-named-not-silent": true, "reg-top-events-malformed-fields-degrade": true, "AC-T-11": true, "AC-T-19": true, "reg-empty-project-honest": true, "AC-T-23": true, "AC-T-21": true, "AC-T-08": true, "AC-T-07": true, "AC-T-18": true, "AC-T-20": true, "AC-T-22": true, "reg-ledger-not-found": true, "AC-T-12": true, "reg-top-render-state-text-cannot-widen-or-escape": true, "AC-T-13": true, "AC-T-14": true, "AC-T-15": true, "AC-T-16": true, "AC-T-17": true}
|