Every ticket as the root of its provenance graph — goal, patch, approval, runs and (as the ticket-class build lands) mandate, manifestations, guarantees, journey, artifacts and children. Rendered through the <<<TicketGraphList>>> tag.
goal
Rewrite ONE unique forge module as entirely new, behavior-preserving, installable,
control-config'd Python, per the FORGE-wide refactor standard #200 (#std).
This ticket covers ONE module — the one identified below — and nothing else. It was filed
against the module's DEDUPLICATED identity, so if that module was copy-pasted across the
forge, every copy is covered by this single ticket.
MODULE KEY : forge-rw:e9deb4e89ce282f4
(the ticketer's idempotency marker — it lives in this ticket's TITLE. Do
not edit the title, or the module will be re-filed as a duplicate.)
IDENTITY : AST-NORMALIZED — codebean's `_ast_norm` (#183), i.e.
ast.dump(ast.parse(ast.unparse(ast.parse(src)))).
Copies of this module that differ only in comments, blank lines,
quoting or spacing carry the SAME module-sha and are covered by THIS
ticket. A differing docstring is real content, so a module whose
docstring differs is a DIFFERENT module with its own ticket.
SIZE : 5338 bytes
EXTENSION : .py
CANONICAL SOURCE (the location this ticket is named for):
repo : agent_tools_suite
path : validate_http.py
ref : HEAD (HEAD)
blob : 992f3302339cb2c5f230b6fc8308090e07778ed1 (git blob sha1)
REFERENCE ONLY — you do NOT need this command. This module's bytes are embedded
COMPLETE in THE ORIGINAL SOURCE block below, and that is what the rewrite is based
on. For a reader who does have forge access:
git --git-dir=/Users/stevenpeterson/code/installed/jazz-project/config/forge/remotes/agent_tools_suite.git cat-file blob 992f3302339cb2c5f230b6fc8308090e07778ed1
THE SAME MODULE ALSO LIVES AT THESE FORGE TIPS: none — this module is unique to
agent_tools_suite among the repos scanned so far.
HISTORICAL OCCURRENCES of this exact blob — every OTHER DISTINCT {repo, path}
the omni-git index (`occ`, joined on the git blob sha1) records this
byte-identical file at, with the index's own `ts` for the first row of each.
Any place this run enumerated LIVE is listed under the forge tips above, not
here. Context for the rewrite, not extra work:
testmonkeyalpha-group/jazz-forge/agent_tools_suite:validate_http.py [occ.ts gitlab]
THE ORIGINAL SOURCE, EMBEDDED — YOU NEED NO FORGE ACCESS TO DO THIS TICKET.
The module's own bytes are reproduced below, inside this ticket. THEY are the
authoritative copy and the behavior-preserving basis: rewrite from them. The
`git --git-dir=... cat-file blob` command under CANONICAL SOURCE above is kept only as a
REFERENCE for a reader who happens to have forge access — running it is NOT part of this
ticket. (Why the bytes are here at all: the pair_loop worker that executes this ticket is
sandboxed to the ticket's own congruency worktree and cannot read the forge's bare repos.
Ticket #346 is the fix that put them on the ticket.)
The block BEGINS at the line
===ORIGINAL SOURCE (verbatim, the behavior-preserving basis)===
and ENDS at the first line
===END SOURCE===
Everything strictly between those two lines is the module — 5338 byte(s), byte for byte, with no re-indentation, no re-wrapping and nothing
elided.
===ORIGINAL SOURCE (verbatim, the behavior-preserving basis)===
#!/usr/bin/env python3
"""validate_http — the FastMCP service enforces the MCP's requirements server-side (un-side-steppable).
Runs against the enforcement seam directly (no live HTTP round-trip needed): a call with no `intent`
is refused before dispatch; a state-changing crank is refused without an open memory ticket AND if
provenance can't be recorded; reads pass. Also proves the enforcement is wired into `call_tool`.
~/code/.venv/bin/python validate_http.py
"""
import asyncio
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import ats_http as A # noqa: E402
P = F = 0
def ok(m): globals().__setitem__("P", P + 1); print(" \033[32m✓\033[0m " + m)
def bad(m): globals().__setitem__("F", F + 1); print(" \033[31m✗ FAIL\033[0m " + m)
def check(c, m): ok(m) if c else bad(m)
def refused(fn):
try:
fn(); return False
except A.GateRefusal:
return True
def main():
print("\n\033[1mats_http — server-side enforcement (un-side-steppable)\033[0m")
# stub provenance so tests never depend on congruency being up
A.record = lambda *a, **k: True
A.open_ticket = lambda: False
# 1. intent required on EVERY call
check(refused(lambda: A.enforce("crankimg_verify", {})),
"no `intent` → refused before dispatch")
check(not refused(lambda: A.enforce("crankimg_verify", {"intent": "read"})),
"with intent → a read is allowed")
# 2. a crank is a state change: allowed at the gate with intent (authorization is now crankd's job)
check(not refused(lambda: A.enforce("crankws_crank", {"intent": "crank"})),
"crankws_crank with intent → allowed at the gate (crankd does the wallet approval)")
# 3. provenance is a HARD precondition for a state change
A.record = lambda *a, **k: False # congruency unreachable
check(refused(lambda: A.enforce("crankws_crank", {"intent": "crank"})),
"state change refused if provenance can't be recorded (not silently dropped)")
A.record = lambda *a, **k: True # reads never blocked by provenance
check(not refused(lambda: A.enforce("crankimg_verify", {"intent": "x"})),
"a read is NOT blocked when provenance is best-effort")
# 4. the enforcement is wired into call_tool (the dispatch path), not bypassable
try:
asyncio.run(A.mcp.call_tool("crankimg_verify", {})) # no intent
check(False, "call_tool enforces intent (should have raised)")
except A.GateRefusal:
check(True, "call_tool routes through enforce() — no-intent call raised GateRefusal")
# 5. the full tool surface is preserved
names = set(asyncio.run(A.mcp.list_tools_names())) if hasattr(A.mcp, "list_tools_names") \
else {t.name for t in asyncio.run(A.mcp.list_tools())}
expect = {"crankws_status", "crankws_read", "crankws_ticket", "crankws_crank",
"crankimg_build", "crankimg_verify", "crankimg_disks", "crankimg_flash", "crankimg_base"}
check(names == expect, "all 9 tools registered — no crankfix, no direct-SQL tool (got %d)" % len(names))
# 6. a read-only tool BODY still behaves (crankws_status reads the repo, offline)
out = A.crankws_status(intent="check")
check("workstation @" in out and "version=" in out, "crankws_status body works (reused leaf logic)")
# 7. wallet-derived tokens: two scoped tokens; the verifier round-trips
import ats_token
m = ats_token.load_map()
check(len(m) == 2 and any("ats:full" in s for s in m.values())
and any("ats:crankws" in s for s in m.values()), "wallet token map has full + crankws scopes")
v = A.WalletTokenVerifier(m)
full_tok = next(t for t, s in m.items() if "ats:full" in s)
at = asyncio.run(v.verify_token(full_tok))
check(at is not None and "ats:full" in at.scopes, "verify_token: valid token -> AccessToken w/ scopes")
check(asyncio.run(v.verify_token("garbage")) is None, "verify_token: unknown token -> None (401)")
# 8. per-tool authorization: enforce() consults the request token's scopes
A.current_scopes = lambda: {"ats:use", "ats:crankws"}
check(not refused(lambda: A.enforce("crankws_status", {"intent": "x"})),
"crankws-scoped token: crankws_* allowed")
check(refused(lambda: A.enforce("crankimg_verify", {"intent": "x"})),
"crankws-scoped token: crankimg_* REFUSED (scope)")
A.current_scopes = lambda: {"ats:use", "ats:full"}
check(not refused(lambda: A.enforce("crankimg_verify", {"intent": "x"})),
"full-scoped token: crankimg_* allowed")
# 9. list_tools is filtered to the token's scope (crankws client sees only its 3 verbs)
A.current_scopes = lambda: {"ats:use", "ats:crankws"}
ws = {t.name for t in asyncio.run(A.mcp.list_tools())}
check(ws == {"crankws_status", "crankws_read", "crankws_ticket", "crankws_crank"},
"crankws token: list_tools shows only the 4 crankws verbs (got %d)" % len(ws))
A.current_scopes = lambda: {"ats:use", "ats:full"}
check(len({t.name for t in asyncio.run(A.mcp.list_tools())}) == 9,
"full token: list_tools shows all 9")
print("\n\033[1m%s — %d passed, %d failed\033[0m" % ("PASS" if not F else "FAIL", P, F))
return 1 if F else 0
if __name__ == "__main__":
sys.exit(main())
===END SOURCE===
THE CALLABLE SURFACE — every callable in the original, lowered with codebean's
`lower_node` (#183). The rewrite must present THIS surface (same names, same
parameters, same defaults) so callers of the original keep working:
ok(m)
bad(m)
check(c, m)
refused(fn)
main()
CAPTURED BEHAVIORAL EXEMPLARS: none. no function in this module is minable — criteria_miner mines only
self-contained pure functions (it refuses anything that mentions a
world-touching or non-deterministic name, plus methods and no-arg
functions), and nothing here qualified
This does NOT weaken the ticket's bar — it MOVES the work: you must write the
equivalence exemplars by hand from the original's behavior before rewriting, and
they are what settles this ticket.
THE STANDARD — #200's four requirements, which this ticket exists to enforce. All four are
the standard's own words; none is optional:
1. BEHAVIOR-PRESERVING, VERIFIED NOT ASSERTED — the deliverable is ENTIRELY NEW source
code (rewritten, not copied). It must reproduce the ORIGINAL's behavior, and the proof
is the exemplars above (captured via codebean lower_* #183 + criteria_miner
IO-exemplars, exactly as #200 specifies).
2. A PYTHON MODULE — regardless of the source language. This module's source is `.py`;
the rewrite is Python either way.
3. INSTALLABLE — a proper package: pyproject.toml, `pip install -e .` works, console
entry points where applicable. WRITE ONLY YOUR OWN MODULE FILE at
forge2/<repo>/<module>.py. Do NOT hand-author pyproject.toml or __init__.py: the
harness DERIVES them ADDITIVELY from the modules present (congruency/tools/
forge_package.py, #410 — the analogue of the tag-registry fix #281), so your unit never
conflicts with, or clobbers, a sibling module of the same forge2/<repo>/ package. A
package-level pyproject includes every .py in the directory, so your module installs
and imports as `<repo>.<module>` the moment it lands.
4. CONTROL-CONFIG'D — NO hardcoded paths or params. A JSON/registry config object drives
it (the install.json / registry.json idiom of this repo), and per note-for-claude a
tool that cannot see its config THROWS rather than guessing a default. Add ONLY your
own `forge2.<repo>.<module>` section to the package's registry.json (create the file if
it is absent); NEVER read, edit or drop a sibling module's section — the sections are
disjoint keys and stay additive that way.
ACCEPTANCE (#200's, per rewrite ticket) — this ticket settles when ALL of:
a. the new source reproduces every captured exemplar above, plus the edge cases you add
for what sampling cannot reach (paste the runs as evidence);
b. it presents the callable surface above, so existing callers keep working;
c. `pip install -e .` installs it clean from its own pyproject.toml;
d. it runs off a config object with ZERO hardcoded settings, and throws without one.
Kind is `build` (a pair: coder + adversarial tester). This ticket hangs off #200 by a
`spawned` edge, so a stop ticket aimed at #200 brakes every rewrite in this programme.
Filed mechanically by checkouts/current/congruency/tools/forge_refactor_ticketer.py under ticket #201, from the forge bare repos at
/Users/stevenpeterson/code/installed/jazz-project/config/forge/remotes. Its original source is embedded above (#346), so this ticket is complete on its own.