@r1z4x/tezgah 1.8.3 → 1.9.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/CHANGELOG.md +35 -1
- package/MANIFEST +6 -0
- package/bin/tezgah-docs +5 -4
- package/bin/tezgah-optimize +338 -0
- package/bin/tezgah-research +2 -2
- package/bin/tezgah-route +6 -2
- package/bin/tezgah-setup +15 -1
- package/bin/tezgah-task +239 -27
- package/bin/tezgah-taste +3 -1
- package/docs/architecture.md +8 -0
- package/docs/clarity-baseline.json +1 -1
- package/docs/environment.md +1 -1
- package/docs/evidence.md +23 -20
- package/docs/gate.md +19 -14
- package/docs/models.md +9 -2
- package/docs/operations.md +9 -5
- package/docs/testing.md +18 -14
- package/hooks/lang/en.json +3 -0
- package/hooks/lang/tr.json +3 -0
- package/hooks/tezgah_context.py +31 -19
- package/hooks/tezgah_gate.py +75 -36
- package/hooks/tezgah_integrity.py +90 -134
- package/hooks/tezgah_models.py +62 -26
- package/hooks/tezgah_paths.py +84 -28
- package/hooks/tezgah_policy.py +10 -4
- package/hooks/tezgah_research.py +14 -8
- package/hooks/tezgah_stopspec.py +36 -83
- package/hooks/tezgah_store.py +7 -0
- package/hooks/tezgah_task.py +104 -35
- package/hosts/opencode/plugins/tezgah.js +136 -0
- package/output-styles/tezgah.md +5 -2
- package/package.json +1 -1
- package/skills/plan-add/SKILL.md +8 -4
- package/skills/tezgah-contract/SKILL.md +14 -0
package/hooks/tezgah_paths.py
CHANGED
|
@@ -267,7 +267,10 @@ def _toplevel(real):
|
|
|
267
267
|
|
|
268
268
|
|
|
269
269
|
def linked_main(path):
|
|
270
|
-
"""The main checkout of the nearest linked worktree holding `path`, else None
|
|
270
|
+
"""The main checkout of the nearest linked worktree holding `path`, else None;
|
|
271
|
+
for a worktree of a bare repository, which has no main checkout, the bare
|
|
272
|
+
git directory (`_bare_repo`): its `.tezgah` is the workspace no `git worktree
|
|
273
|
+
remove`, detach or switch touches (plan 098).
|
|
271
274
|
|
|
272
275
|
Nearest only: `worktrees` asks it about the repository it was handed, so a
|
|
273
276
|
repo nested inside a worktree (a vendored clone, a submodule) answers None and
|
|
@@ -275,8 +278,8 @@ def linked_main(path):
|
|
|
275
278
|
uses. Read from the `.git` pointer file (`gitdir: <main>/.git/worktrees/
|
|
276
279
|
<name>`), never from git: `root_for` runs on every gate call and a session
|
|
277
280
|
start's git forks are pinned. A plain checkout (`.git` is a directory), a
|
|
278
|
-
submodule (a `/modules/` pointer) and a
|
|
279
|
-
answer None."""
|
|
281
|
+
submodule (a `/modules/` pointer) and a worktree of a git directory kept
|
|
282
|
+
apart from its main checkout (`--separate-git-dir`) answer None."""
|
|
280
283
|
top = _toplevel(os.path.realpath(path))
|
|
281
284
|
if not top:
|
|
282
285
|
return None
|
|
@@ -314,27 +317,72 @@ def _linked_main_at(top):
|
|
|
314
317
|
gitdir = os.path.normpath(os.path.join(top, line[len("gitdir:"):].strip()))
|
|
315
318
|
dotgit = os.path.dirname(os.path.dirname(gitdir))
|
|
316
319
|
if os.path.basename(os.path.dirname(gitdir)) != "worktrees" \
|
|
317
|
-
or os.path.basename(dotgit) != ".git" \
|
|
318
320
|
or not os.path.isfile(os.path.join(gitdir, "commondir")):
|
|
319
321
|
return None
|
|
320
|
-
|
|
322
|
+
if os.path.basename(dotgit) == ".git":
|
|
323
|
+
return os.path.realpath(os.path.dirname(dotgit))
|
|
324
|
+
return os.path.realpath(dotgit) if _bare_repo(dotgit) else None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _bare_repo(path):
|
|
328
|
+
"""True when `path` is the git directory of a bare repository: a `HEAD`, an
|
|
329
|
+
`objects/` and `core.bare` set in its config, read without a git fork."""
|
|
330
|
+
if not (os.path.isfile(os.path.join(path, "HEAD"))
|
|
331
|
+
and os.path.isdir(os.path.join(path, "objects"))):
|
|
332
|
+
return False
|
|
333
|
+
section = ""
|
|
334
|
+
try:
|
|
335
|
+
with open(os.path.join(path, "config"), encoding="utf-8", errors="replace") as fh:
|
|
336
|
+
for line in fh:
|
|
337
|
+
line = line.strip()
|
|
338
|
+
if not line or line[0] in "#;":
|
|
339
|
+
continue
|
|
340
|
+
if line.startswith("["):
|
|
341
|
+
section = line.strip("[]").split('"')[0].strip().lower()
|
|
342
|
+
continue
|
|
343
|
+
key, eq, value = line.partition("=")
|
|
344
|
+
if section == "core" and key.strip().lower() == "bare":
|
|
345
|
+
return not eq or value.strip().lower() in ("true", "yes", "on", "1")
|
|
346
|
+
except OSError:
|
|
347
|
+
pass
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _admin_dir(main):
|
|
352
|
+
"""The git directory of the main checkout `main` (`<main>/.git`), or `main`
|
|
353
|
+
itself when it is a bare repository's; None for neither."""
|
|
354
|
+
dot = os.path.join(main, ".git")
|
|
355
|
+
if os.path.isdir(dot):
|
|
356
|
+
return dot
|
|
357
|
+
return main if _bare_repo(main) else None
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def work_tree(repo, cwd=None):
|
|
361
|
+
"""The checkout whose project files a command about `repo` reads: `repo`
|
|
362
|
+
itself, unless it is a bare repository's git directory, which holds the
|
|
363
|
+
workspace and no project file (plan 098) - then the worktree of it holding
|
|
364
|
+
`cwd` (the process's by default), else `repo`."""
|
|
365
|
+
if not _bare_repo(repo):
|
|
366
|
+
return repo
|
|
367
|
+
top = _toplevel(os.path.realpath(cwd or os.getcwd()))
|
|
368
|
+
return top if top and linked_main(top) == os.path.realpath(repo) else repo
|
|
321
369
|
|
|
322
370
|
|
|
323
371
|
def worktrees(repo):
|
|
324
372
|
"""Every checkout of the repository `repo` is in, main first, as real paths;
|
|
325
|
-
[] outside a git checkout.
|
|
373
|
+
[] outside a git checkout. A bare repository's git directory stands first,
|
|
374
|
+
in the main checkout's place (`linked_main`).
|
|
326
375
|
|
|
327
376
|
Fork-free, from `<main>/.git/worktrees/*/gitdir` (each names a linked
|
|
328
377
|
worktree's `.git`). An entry whose target is gone - deleted without `git
|
|
329
378
|
worktree remove` - is skipped: git itself keeps it, marked prunable, until
|
|
330
|
-
`git worktree prune`.
|
|
331
|
-
|
|
332
|
-
main = linked_main(
|
|
333
|
-
if
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
base = os.path.join(main, ".git", "worktrees")
|
|
379
|
+
`git worktree prune`."""
|
|
380
|
+
real = os.path.realpath(repo)
|
|
381
|
+
main = linked_main(real) or (real if _bare_repo(real) else _toplevel(real))
|
|
382
|
+
admin = _admin_dir(main) if main else None
|
|
383
|
+
if not admin:
|
|
384
|
+
return []
|
|
385
|
+
base = os.path.join(admin, "worktrees")
|
|
338
386
|
out = [main]
|
|
339
387
|
try:
|
|
340
388
|
names = sorted(os.listdir(base))
|
|
@@ -1225,14 +1273,18 @@ def _index_tracks_workspace(root):
|
|
|
1225
1273
|
|
|
1226
1274
|
def ensure_workspace(repo):
|
|
1227
1275
|
"""`<repo>/.tezgah`, created, ignored by the project and holding its own git
|
|
1228
|
-
repository; None when `repo` is not a git work tree or a
|
|
1276
|
+
repository; None when `repo` is not a git work tree or a bare repository's
|
|
1277
|
+
git directory, or a step failed.
|
|
1229
1278
|
|
|
1230
1279
|
The ignore lines are appended to the project's `.gitignore` only when
|
|
1231
|
-
missing, after every line the user wrote, and nothing is staged
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
and the
|
|
1235
|
-
|
|
1280
|
+
missing, after every line the user wrote, and nothing is staged; a bare
|
|
1281
|
+
repository gets none, since no worktree reads its git directory as content
|
|
1282
|
+
(its worktrees' links are excluded by `link_workspace`). A second call costs
|
|
1283
|
+
three stats and one file read: the subprocess runs only when `.tezgah/.git`
|
|
1284
|
+
does not exist yet. Fail-open: a read-only tree returns None and the session
|
|
1285
|
+
goes on."""
|
|
1286
|
+
bare = _bare_repo(repo)
|
|
1287
|
+
if not bare and not os.path.exists(os.path.join(repo, ".git")):
|
|
1236
1288
|
return None
|
|
1237
1289
|
ws = workspace(repo)
|
|
1238
1290
|
# A `.tezgah` that is a symlink (or anything but a directory) came with the
|
|
@@ -1255,7 +1307,8 @@ def ensure_workspace(repo):
|
|
|
1255
1307
|
except FileNotFoundError:
|
|
1256
1308
|
body = ""
|
|
1257
1309
|
have = {line.strip().strip("/") for line in body.splitlines()}
|
|
1258
|
-
missing = [
|
|
1310
|
+
missing = [] if bare else [line for line in WORKSPACE_IGNORES
|
|
1311
|
+
if line.strip("/") not in have]
|
|
1259
1312
|
if missing:
|
|
1260
1313
|
with open(path, "a", encoding="utf-8") as fh:
|
|
1261
1314
|
fh.write(("\n" if body and not body.endswith("\n") else "")
|
|
@@ -1273,16 +1326,17 @@ def ensure_workspace(repo):
|
|
|
1273
1326
|
|
|
1274
1327
|
def link_workspace(checkout, main):
|
|
1275
1328
|
"""`<checkout>/.tezgah` as a link to the main checkout's workspace, for a
|
|
1276
|
-
linked worktree (plan 100)
|
|
1277
|
-
|
|
1329
|
+
linked worktree (plan 100), or to a bare repository's when `main` is its git
|
|
1330
|
+
directory (plan 098): a relative `.tezgah/...` write from the worktree then
|
|
1331
|
+
lands in the one workspace every checkout reads, instead of a second
|
|
1278
1332
|
`.tezgah` that `git worktree remove` deletes. `/.tezgah` goes to the
|
|
1279
1333
|
repository's `info/exclude`, since the project's `/.tezgah/` ignores only a
|
|
1280
1334
|
directory. Leaves any existing `.tezgah` alone; None when nothing was made."""
|
|
1281
1335
|
link, target = workspace(checkout), workspace(main)
|
|
1282
|
-
|
|
1283
|
-
if
|
|
1284
|
-
or not os.path.isdir(os.path.join(main, ".git"))):
|
|
1336
|
+
admin = _admin_dir(main)
|
|
1337
|
+
if os.path.lexists(link) or not os.path.isdir(target) or not admin:
|
|
1285
1338
|
return None
|
|
1339
|
+
exclude = os.path.join(admin, "info", "exclude")
|
|
1286
1340
|
try:
|
|
1287
1341
|
os.makedirs(os.path.dirname(exclude), exist_ok=True)
|
|
1288
1342
|
try:
|
|
@@ -1319,8 +1373,10 @@ def _git_config_paths(repo):
|
|
|
1319
1373
|
gitdir = gitdir if os.path.isabs(gitdir) else os.path.join(repo, gitdir)
|
|
1320
1374
|
out += [os.path.join(gitdir, "config"),
|
|
1321
1375
|
os.path.join(gitdir, "..", "..", "config")]
|
|
1322
|
-
|
|
1323
|
-
|
|
1376
|
+
else:
|
|
1377
|
+
admin = _admin_dir(repo)
|
|
1378
|
+
if admin:
|
|
1379
|
+
out.append(os.path.join(admin, "config"))
|
|
1324
1380
|
out.append(os.path.join(HOME, ".gitconfig"))
|
|
1325
1381
|
out.append(os.path.join(CONFIG_DIR_GIT, "git", "config"))
|
|
1326
1382
|
return out
|
package/hooks/tezgah_policy.py
CHANGED
|
@@ -233,7 +233,10 @@ in a cheaper, deferred, stubbed or partial stand-in for a requested item;
|
|
|
233
233
|
silently narrowing the scope; deciding an item is unnecessary, "YAGNI" or "out
|
|
234
234
|
of scope" and dropping it; a token gesture (a comment, a TODO, a mention)
|
|
235
235
|
reported as if the item were done; stopping early because the work got long or
|
|
236
|
-
the rest is hard.
|
|
236
|
+
the rest is hard. Its kind is the ceiling: a research, analysis, review or plan
|
|
237
|
+
ask ends at the written plan, and building that plan is a class-F step (see
|
|
238
|
+
"Ask policy"): asked, and never started in the turn that wrote it. A finding
|
|
239
|
+
outside the ask becomes a plan too, never code. A requested item may leave scope only when the user takes it
|
|
237
240
|
out. If an item looks redundant, impossible or genuinely out of scope, put it in
|
|
238
241
|
the approval batch with "keep it" as the recommended default and keep working on
|
|
239
242
|
it: the work never proceeds on a cut. An unsafe item is a class-F step (see "Ask
|
|
@@ -298,7 +301,7 @@ whether the user already decided it (named it this session, or granted it).
|
|
|
298
301
|
| C. Reversible choice the evidence settles | a fix's scope inside the ask, naming, a palette when tokens exist | decide from evidence; real taste goes in the batch with a default, taken this turn |
|
|
299
302
|
| D. Intent fork | two readings of the ask with different deliverables | batch with a default; do the shared part first and proceed on the default |
|
|
300
303
|
| E. Outward on the user's own remote | push, PR, tag, release, publishing the user's own package | act on a grant written in chat ({GRANT_EXAMPLES}); else ask once per session, never again after a grant |
|
|
301
|
-
| F. Never on a default | force push or history rewrite on a shared ref; deleting data the session did not make; production deploy, stop or migration; a credential, account or identity change; sending to a third party; a spend over `spend_cap
|
|
304
|
+
| F. Never on a default | force push or history rewrite on a shared ref; deleting data the session did not make; production deploy, stop or migration; a credential, account or identity change; sending to a third party; a spend over `spend_cap`; building a plan this turn wrote from a research, analysis, review or plan ask | do all other work first, ask, and wait: silence is no consent, and only an answer that names the action and its target clears it |
|
|
302
305
|
| G. {ACCESS} | a credential or grant no tool supplies | take every route that needs none first, then ask once, naming what it unlocks |
|
|
303
306
|
|
|
304
307
|
A grant is read from the user's prompt and recorded for the session; "artık
|
|
@@ -836,7 +839,9 @@ examples. Off: `tezgah-adhd off`, or the repo's `.no-adhd`.
|
|
|
836
839
|
|
|
837
840
|
**Deliver the whole ask; never the shortcut.** The request defines the
|
|
838
841
|
deliverable: every named item is in scope until the user says otherwise, and the
|
|
839
|
-
ask is a floor, not a ceiling
|
|
842
|
+
ask is a floor, not a ceiling - its kind is one: a research, analysis, review or
|
|
843
|
+
plan ask ends at the written plan, and building it waits for the user's answer.
|
|
844
|
+
Ponytail shrinks the solution, never the request.
|
|
840
845
|
FORBIDDEN: swapping in a cheaper, deferred or partial stand-in for what was
|
|
841
846
|
asked; silently narrowing scope; deciding a requested item is
|
|
842
847
|
"unnecessary"/"YAGNI" and dropping it; a token gesture reported as done; stopping
|
|
@@ -880,7 +885,8 @@ lines: item - blocker - evidence - what unblocks it. No blockers: no heading.
|
|
|
880
885
|
Work and process - the order, "shall I go on", a check, a plan, a consult
|
|
881
886
|
member, a spend within `spend_cap`, a local merge of the session's branch,
|
|
882
887
|
removing its worktree - are taken and stated in one line, never asked; a taste
|
|
883
|
-
or intent fork goes in the batch with a default taken this turn
|
|
888
|
+
or intent fork goes in the batch with a default taken this turn, but building a
|
|
889
|
+
plan this turn wrote waits for the answer. An outward step
|
|
884
890
|
on the user's own remote (push, PR, release, their package) runs on a grant
|
|
885
891
|
({GRANT_EXAMPLES}), else is asked once. Never on a
|
|
886
892
|
default - ask and wait for an answer naming action and target: a force push,
|
package/hooks/tezgah_research.py
CHANGED
|
@@ -2095,7 +2095,8 @@ def append_claim(repo, slug, claim):
|
|
|
2095
2095
|
# The judgement runs under the lock and reads the ids from the descriptor
|
|
2096
2096
|
# holding it (`_claim_ids`): the ids a claim is judged against are the ids
|
|
2097
2097
|
# the append commits beside.
|
|
2098
|
-
problems = claim_problems(claim, base, repo,
|
|
2098
|
+
problems = claim_problems(claim, base, tp.work_tree(repo),
|
|
2099
|
+
held=_claim_ids(base, handle))
|
|
2099
2100
|
if problems:
|
|
2100
2101
|
if created and os.path.getsize(path) == 0:
|
|
2101
2102
|
os.remove(path)
|
|
@@ -3556,7 +3557,9 @@ def check_line(repo, slug, git=True, strict=False, code=None):
|
|
|
3556
3557
|
`strict=True` turns every guarantee the checker cannot decide into an error.
|
|
3557
3558
|
`code` is the checkout the check runs in when it is not `repo`: the line
|
|
3558
3559
|
lives in the main checkout's workspace (plan 097) and its citations and
|
|
3559
|
-
proofs resolve against the checkout whose branch holds the code
|
|
3560
|
+
proofs resolve against the checkout whose branch holds the code; unnamed, a
|
|
3561
|
+
bare repository's worktree holding the cwd (`tp.work_tree`, plan 098)."""
|
|
3562
|
+
code = code or tp.work_tree(repo)
|
|
3560
3563
|
base = line_dir(repo, slug)
|
|
3561
3564
|
errors, warnings = [], []
|
|
3562
3565
|
if not os.path.isdir(base):
|
|
@@ -3723,12 +3726,13 @@ def done_seal(repo, slug):
|
|
|
3723
3726
|
return _check_seal(base)[0], True
|
|
3724
3727
|
|
|
3725
3728
|
|
|
3726
|
-
def failing(repo, git=False):
|
|
3729
|
+
def failing(repo, git=False, code=None):
|
|
3727
3730
|
"""(slug, error) pairs, for a one-line session note. Structural only by
|
|
3728
3731
|
default: the session context must not pay for git history. A line under
|
|
3729
3732
|
`done/` is read by its seal hashes alone (`check` with `all_lines=False`), and
|
|
3730
|
-
an unsealed one adds nothing (plan 058 part 4)."""
|
|
3731
|
-
return [(slug, err) for slug, row in check(repo, git=git, all_lines=False
|
|
3733
|
+
an unsealed one adds nothing (plan 058 part 4). `code` as for `check_line`."""
|
|
3734
|
+
return [(slug, err) for slug, row in check(repo, git=git, all_lines=False,
|
|
3735
|
+
code=code).items()
|
|
3732
3736
|
for err in row["errors"]]
|
|
3733
3737
|
|
|
3734
3738
|
|
|
@@ -4337,6 +4341,7 @@ def check_orx(repo, orx, strict=False):
|
|
|
4337
4341
|
return {"errors": [], "warnings":
|
|
4338
4342
|
["orx is not installed, so the run command registered against this "
|
|
4339
4343
|
"repository was not read (TEZGAH_ORX_BIN overrides the lookup)"]}
|
|
4344
|
+
repo = tp.work_tree(repo)
|
|
4340
4345
|
out, err = _run([orx, "projects", "--json"], cwd=repo)
|
|
4341
4346
|
if err:
|
|
4342
4347
|
return {"errors": [], "warnings": ["orx projects could not be read (%s)" % err]}
|
|
@@ -4526,7 +4531,7 @@ def _migrate_claims(base, repo, dry_run, problems):
|
|
|
4526
4531
|
if claim.get("kind") in KINDS:
|
|
4527
4532
|
out.append(raw)
|
|
4528
4533
|
continue
|
|
4529
|
-
kind = derive_kind(claim, (base, repo))
|
|
4534
|
+
kind = derive_kind(claim, (base, tp.work_tree(repo)))
|
|
4530
4535
|
if kind is None:
|
|
4531
4536
|
problems.append("claim %s: no kind derived - its proof names no "
|
|
4532
4537
|
"artifact to be a proof of" % label)
|
|
@@ -5498,7 +5503,8 @@ def arena_record(repo, problem, budget, evaluator="", metric=""):
|
|
|
5498
5503
|
arena = {"problem": problem, "budget": budget}
|
|
5499
5504
|
if evaluator:
|
|
5500
5505
|
arena.update({"evaluator": evaluator,
|
|
5501
|
-
"evaluator_sha256": _digest(os.path.join(repo
|
|
5506
|
+
"evaluator_sha256": _digest(os.path.join(tp.work_tree(repo),
|
|
5507
|
+
evaluator)),
|
|
5502
5508
|
"metric": metric})
|
|
5503
5509
|
return arena
|
|
5504
5510
|
|
|
@@ -5544,7 +5550,7 @@ def evaluator_problem(repo, state):
|
|
|
5544
5550
|
if not _text(path):
|
|
5545
5551
|
return None
|
|
5546
5552
|
try:
|
|
5547
|
-
now = _digest(os.path.join(repo, path))
|
|
5553
|
+
now = _digest(os.path.join(tp.work_tree(repo), path))
|
|
5548
5554
|
except OSError:
|
|
5549
5555
|
now = None
|
|
5550
5556
|
locked = str(arena.get("evaluator_sha256") or "")
|
package/hooks/tezgah_stopspec.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""The Stop rule's evidence fold as a
|
|
3
|
-
roadmap R16
|
|
2
|
+
"""The Stop rule's evidence fold, declared as a past-time temporal spec (plan 063,
|
|
3
|
+
roadmap R16; the fold since plan 101 replaced the imperative one it ran in
|
|
4
|
+
shadow beside).
|
|
4
5
|
|
|
5
6
|
**What is declared.** `ROW_ATOMS` are the ten row-local predicates the spec reads,
|
|
6
7
|
each computed once per row by the readers `tezgah_integrity` already has.
|
|
@@ -8,28 +9,26 @@ each computed once per row by the readers `tezgah_integrity` already has.
|
|
|
8
9
|
of the trace: `("S", a, b)` is "a since b" (b held at some row and a at every row
|
|
9
10
|
after it), `("O", a)` is "once a", plus `not`/`and`/`or`. `ORDER` is the
|
|
10
11
|
decision list: the first class whose condition holds is the verdict, in the
|
|
11
|
-
order the
|
|
12
|
+
order the Stop rule refuses them (docs/evidence.md, classes 5-10).
|
|
12
13
|
|
|
13
14
|
**What is not a formula, stated so the bounds cannot be gamed.**
|
|
14
|
-
- The scope selector is `tezgah_integrity._stop_block`,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
fold the selector calls.
|
|
15
|
+
- The scope selector is `tezgah_integrity._stop_block`, which calls `fold`:
|
|
16
|
+
which rows the fold reads (this turn, the session before this turn, the
|
|
17
|
+
settled-bookkeeping and idle-turn claim fallbacks), the lost-began path, and
|
|
18
|
+
the "evidence tampered" guard (a `ledger_damage` row says the trace is not a
|
|
19
|
+
trace; it is checked before any fold).
|
|
20
20
|
- `SCOPE_ATOMS`: the turn marker `T`, which scopes "partial failure" to the
|
|
21
21
|
newest turn of the rows given (`_partial_state`).
|
|
22
22
|
- The reply atoms - the external-state claim and the selector's work flag - are
|
|
23
23
|
constants over one trace, passed in, never read off a row.
|
|
24
|
-
- `ESCAPES` lists, before
|
|
25
|
-
row-local and count against the "<= 3" bar; `NOT_COUNTED` lists the rest
|
|
26
|
-
the candidates plan 063 named and why each does not count.
|
|
27
|
-
|
|
28
|
-
**The monitor** (`
|
|
29
|
-
row, then each formula's value from its value at the previous row. Nothing
|
|
30
|
-
persisted between Stops. `
|
|
31
|
-
|
|
32
|
-
disagreement into an exception, for the test suite.
|
|
24
|
+
- `ESCAPES` lists, fixed before the first shadow row, the predicates that are
|
|
25
|
+
not row-local and count against the "<= 3" bar; `NOT_COUNTED` lists the rest
|
|
26
|
+
of the candidates plan 063 named and why each does not count.
|
|
27
|
+
|
|
28
|
+
**The monitor** (`verdict`) is one in-process pass over the rows at Stop: atoms
|
|
29
|
+
per row, then each formula's value from its value at the previous row. Nothing
|
|
30
|
+
is persisted between Stops. `fold` hands the class to
|
|
31
|
+
`tezgah_integrity._evidence_text`, which words the refusal.
|
|
33
32
|
"""
|
|
34
33
|
import os
|
|
35
34
|
import sys
|
|
@@ -39,8 +38,6 @@ if HERE not in sys.path:
|
|
|
39
38
|
sys.path.insert(0, HERE)
|
|
40
39
|
import tezgah_integrity as ti # noqa: E402
|
|
41
40
|
|
|
42
|
-
STRICT = "TEZGAH_STOPSPEC_STRICT"
|
|
43
|
-
ROW_KIND = "stop_spec"
|
|
44
41
|
|
|
45
42
|
ROW_ATOMS = (
|
|
46
43
|
"Vany", # a verify-family row: verify, verify_ok, verify_fail (`_last_verify`)
|
|
@@ -107,11 +104,7 @@ NOT_COUNTED = (
|
|
|
107
104
|
# given (`tezgah_integrity._admitted`: a check-repair class cleared after a check
|
|
108
105
|
# that failed since the newest change), and `residual work` is a selector verdict
|
|
109
106
|
# asked at every exit that would end the turn (`_residual_block`). Both reach the
|
|
110
|
-
# spec's verdict through the
|
|
111
|
-
|
|
112
|
-
# GO 2's live floor: shadow rows needed before the 99% bar is read. At 400 a
|
|
113
|
-
# 99% rate is 4 disagreements, so one more or less moves it by 0.25 pp.
|
|
114
|
-
MIN_LIVE_N = 400
|
|
107
|
+
# spec's verdict through the selector, so neither is a formula here.
|
|
115
108
|
|
|
116
109
|
|
|
117
110
|
def _nodes(formula, out):
|
|
@@ -249,13 +242,22 @@ class Trace:
|
|
|
249
242
|
return out
|
|
250
243
|
|
|
251
244
|
|
|
252
|
-
def verdict(rows, worked, external
|
|
245
|
+
def verdict(rows, worked, external):
|
|
253
246
|
"""The class `ORDER` picks, or None to allow; a formula is evaluated only
|
|
254
|
-
when the list reaches it.
|
|
255
|
-
|
|
247
|
+
when the list reaches it. `worked` is the selector's work flag, `external`
|
|
248
|
+
the reply's external-state claim or None.
|
|
249
|
+
|
|
250
|
+
The order is the classes' contract: the newest check failing decides before
|
|
251
|
+
anything else; a partial failure, then a UI write with no screen or design
|
|
252
|
+
proof newer than it, are stricter questions about the same rows than
|
|
253
|
+
freshness; a stale pass has another repair than none at all; and the
|
|
254
|
+
external read is asked last, only where the turn would otherwise end, so
|
|
255
|
+
it never changes the class another rule refused under. A screen proof
|
|
256
|
+
stands where a pass stands, and an external read only for its own claim."""
|
|
257
|
+
trace = Trace(rows, external)
|
|
256
258
|
|
|
257
259
|
def holds(name):
|
|
258
|
-
return
|
|
260
|
+
return _TABLE[name](trace)
|
|
259
261
|
|
|
260
262
|
for cls in ("check failed", "partial failure", "no ui_ok"):
|
|
261
263
|
if holds(cls):
|
|
@@ -271,57 +273,8 @@ def verdict(rows, worked, external, table=None):
|
|
|
271
273
|
return "no external read" if unread else None
|
|
272
274
|
|
|
273
275
|
|
|
274
|
-
def fold(rows, worked, external, where="this turn"
|
|
275
|
-
"""
|
|
276
|
-
text
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return (cls, cls)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def judge(text, session_id, rows, cwd, shape, table=None):
|
|
283
|
-
"""(the spec's verdict class, whether the selector reached the fold) for one
|
|
284
|
-
Stop: the shared selector with the spec as its fold. A verdict the selector
|
|
285
|
-
made alone (a shape class, "evidence tampered", a lost check) is the
|
|
286
|
-
imperative one by construction."""
|
|
287
|
-
reached = []
|
|
288
|
-
|
|
289
|
-
def spec_fold(r, w, e, where="this turn"):
|
|
290
|
-
reached.append(True)
|
|
291
|
-
return fold(r, w, e, where, table)
|
|
292
|
-
|
|
293
|
-
cls = ti._stop_block(text, session_id, rows=rows, cwd=cwd, shape=shape,
|
|
294
|
-
fold=spec_fold)[0]
|
|
295
|
-
return cls, bool(reached)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def check(imperative, text, session_id, rows, cwd, shape):
|
|
299
|
-
"""(spec class, agree, fold reached) for one Stop; raises under
|
|
300
|
-
`TEZGAH_STOPSPEC_STRICT` when the two disagree."""
|
|
301
|
-
spec, reached = judge(text, session_id, rows, cwd, shape)
|
|
302
|
-
agree = spec == imperative
|
|
303
|
-
if not agree and os.environ.get(STRICT):
|
|
304
|
-
raise AssertionError("stop spec disagrees: imperative %r, spec %r"
|
|
305
|
-
% (imperative, spec))
|
|
306
|
-
return spec, agree, reached
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def shadow(imperative, text, session_id, rows, cwd, shape, key):
|
|
310
|
-
"""Record the spec's verdict beside the imperative one as one `stop_spec`
|
|
311
|
-
row per reply per turn: `agree fold: <class>`, `disagree selector: <imp> ->
|
|
312
|
-
<spec>` and so on, where `fold` says the selector reached the fold and
|
|
313
|
-
`selector` says it decided alone (GO 2 reads only the first). Never returns
|
|
314
|
-
a verdict: the caller's is final. A failure of the spec is recorded as
|
|
315
|
-
`error: <type>` and re-raised only under the strict switch."""
|
|
316
|
-
if any(r.get("kind") == ROW_KIND and r.get("id") == key for r in rows):
|
|
317
|
-
return
|
|
318
|
-
try:
|
|
319
|
-
spec, agree, reached = check(imperative, text, session_id, rows, cwd, shape)
|
|
320
|
-
where = "fold" if reached else "selector"
|
|
321
|
-
detail = ("agree %s: %s" % (where, imperative or "ok") if agree else
|
|
322
|
-
"disagree %s: %s -> %s" % (where, imperative or "ok", spec or "ok"))
|
|
323
|
-
except Exception as exc:
|
|
324
|
-
if os.environ.get(STRICT):
|
|
325
|
-
raise
|
|
326
|
-
detail = "error: %s" % type(exc).__name__
|
|
327
|
-
ti.note(session_id, ROW_KIND, detail, id=key)
|
|
276
|
+
def fold(rows, worked, external, where="this turn"):
|
|
277
|
+
"""The evidence fold `tezgah_integrity._stop_block` calls: (class, block
|
|
278
|
+
text) or (None, None). The class is the spec's (`verdict`); the text is the
|
|
279
|
+
one `tezgah_integrity._evidence_text` words that class with."""
|
|
280
|
+
return ti._evidence_text(verdict(rows, worked, external), rows, external, where)
|
package/hooks/tezgah_store.py
CHANGED
|
@@ -54,6 +54,13 @@ TASTE_ROWS = {
|
|
|
54
54
|
"defects": ("id", "session", "day"),
|
|
55
55
|
"labels": ("id", "provider", "day"),
|
|
56
56
|
"injected": ("session", "day"),
|
|
57
|
+
# the verification-cost decision (plan 109): one row per measured run of one
|
|
58
|
+
# command at one sha, and one row per push of one sha. The tier map is read
|
|
59
|
+
# from these measured medians rather than declared, and the coalescing window
|
|
60
|
+
# is read from the pushes - so a second session in the same checkout reads the
|
|
61
|
+
# first one's measurements instead of re-deriving them.
|
|
62
|
+
"checks": ("repo", "sha", "command", "seconds", "exit", "ts"),
|
|
63
|
+
"pushes": ("repo", "sha", "branch", "at"),
|
|
57
64
|
}
|
|
58
65
|
IMPORT_SCHEMA = """
|
|
59
66
|
CREATE TABLE IF NOT EXISTS imported (name TEXT NOT NULL, dev INTEGER NOT NULL,
|