Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 82 additions & 48 deletions .stomata/lessons.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
{
"note": "Seeded 2026-08-19 from the review record of 2026-07-24 to 2026-08-19. Occurrences are things that actually happened and cost time, each traceable to a dated review document in the agstack workplan. Nothing speculative is recorded here: a lesson with no incident behind it becomes noise, and noise is what discredits the checks that were earned.",
"$stomata_artifact": "lessons_ledger",
"schema_version": "1.0.0",
"promotion_threshold": 2,
"note": "Seeded 2026-08-19 from the review record of 2026-07-24 to 2026-08-19. Occurrences are things that actually happened and cost time, each traceable to a dated review document in the agstack workplan. Nothing speculative is recorded here: a lesson with no incident behind it becomes noise, and noise is what discredits the checks that were earned.",
"updated_at": "2026-10-11T07:23:08+00:00",
"lessons": [
{
"recurrence_key": "gate-reports-green-while-blind",
"trigger": "A check, test or assertion reports success.",
"directive": "Establish that it can fail. Show it failing on purpose, or show the count it produces changing when the underlying thing changes. A gate that cannot distinguish good from bad is worse than no gate, because it is believed.",
"kind": "mechanized",
"enforced_by": [
"harness/stomata/checks.py",
"harness/tests/test_stomata.py"
],
"occurrences": [
{
"date": "2026-07-24",
Expand Down Expand Up @@ -59,19 +56,17 @@
"what": "The lint ratchet reported '0 violation(s), down from 36' while measuring nothing. ruff was absent from the interpreter STOMATA_PYTHON names, the failure went to stderr, and run_lint counts stdout lines -- so zero lines read as zero violations, the best possible result. It hid a real rise from 36 to 38 introduced by the change it was gating. Fixed in the harness: a non-zero exit with no diagnostics is now reported as blind rather than clean, verified across six outcomes.",
"where": "harness/stomata/run.py:run_lint, checks.py:check_lint_ratchet"
}
],
"enforced_by": [
"harness/stomata/checks.py",
"harness/tests/test_stomata.py"
]
},
{
"recurrence_key": "twin-divergence",
"trigger": "One fact is represented in two places -- a mirror and the system it mirrors, a function and its caller, the same logic in two packages, a value in two config files.",
"directive": "Change both in the same commit, and add a test that fails when they disagree. Do not rely on remembering the second one, because the second one is what gets forgotten.",
"kind": "mechanized",
"enforced_by": [
"migration/tests/test_schema_drift.py::test_mirrored_constraints_match_the_real_schema",
"migration/tests/test_points_are_importable.py",
"stomata.json",
"app/tests/test_point_polygon_peers.py::test_a_point_in_an_upload_is_the_same_plot_as_the_same_point_sent_alone"
],
"occurrences": [
{
"date": "2026-08-12",
Expand Down Expand Up @@ -113,16 +108,19 @@
"what": "Registering a point was implemented twice. /register-point recorded the declared area, wrote geometry_kind and resolved a re-survey by distance; the Point branch of /register-field-boundaries-geojson did none of the three, so the same farm got a different kind of plot depending on whether it arrived alone or in an upload. The bulk branch even carried a comment explaining that points get no resolution pass, which had been true of the single path that morning.",
"where": "app/routers/field_registration.py"
}
],
"enforced_by": [
"migration/tests/test_schema_drift.py::test_mirrored_constraints_match_the_real_schema",
"migration/tests/test_points_are_importable.py",
"stomata.json",
"app/tests/test_point_polygon_peers.py::test_a_point_in_an_upload_is_the_same_plot_as_the_same_point_sent_alone"
]
},
{
"recurrence_key": "label-diverges-from-outcome",
"trigger": "You are naming or counting what a run did, in a report, a log line, a status field or a document.",
"directive": "Make the word mean one outcome. If a category can contain both a success and a failure, split it. Assert that the categories sum to the total and do not overlap, because every individual number can be computed correctly and the totals still describe a run that did not happen.",
"kind": "mechanized",
"enforced_by": [
"migration/tests/test_report_arithmetic.py"
],
"occurrences": [
{
"date": "2026-08-14",
Expand All @@ -149,17 +147,16 @@
"what": "resolved_child_of was presented as a peer of imported_new when it is a subset, so 74 fields summed to 75 and read as one field processed twice.",
"where": "migration/tests/test_report_arithmetic.py"
}
],
"enforced_by": [
"migration/tests/test_report_arithmetic.py"
]
},
{
"recurrence_key": "claim-not-in-the-evidence",
"trigger": "You are about to quote a number, or cite a run, screenshot or log as evidence.",
"directive": "Open the artifact and find the number in it. Cite the run that produced it, at the commit under review. A figure that cannot be located in the evidence is a recollection, and recollections drift toward what we hoped.",
"kind": "mechanized",
"enforced_by": [
"harness/stomata/packet.py",
"harness/tests/test_stomata.py"
],
"occurrences": [
{
"date": "2026-08-14",
Expand All @@ -181,40 +178,48 @@
"what": "A summary quoted a pre-fix reconciliation of 13,658 + 14,594 + 30 while the attached log showed 27,978 + 275 + 29.",
"where": "packets/evidence/import-output.txt"
}
],
"enforced_by": [
"harness/stomata/packet.py",
"harness/tests/test_stomata.py"
]
},
{
"recurrence_key": "guard-weakened-to-pass",
"trigger": "A guard, assertion or lint rule stands between you and a change you believe is correct.",
"directive": "Change the claim or change the code, never quietly narrow the guard. If the guard is genuinely stale, say so in the commit message and re-record the baseline, so that weakening it costs a sentence someone can read.",
"recurrence_key": "second-door-to-a-gated-answer",
"trigger": "Adding a route, header, flag or claim that answers a question an existing route already gates, or reviewing one.",
"directive": "Every door to a gated answer carries the same gate: enumerate the routes whose answer is gated and require each handler to call the gate and read no flag. A convenience path, a service header, an environment variable or a self-asserted claim that reaches the same data is a second key, and a second key nobody tracks is the one that gets used.",
"kind": "mechanized",
"enforced_by": [
"harness/stomata/checks.py",
"harness/tests/test_lessons.py::test_removing_an_assertion_from_a_guard_fails"
],
"occurrences": [
{
"date": "2026-08-12",
"what": "ruff.toml's ignore list was suppressing rule families including B008, so the linter was configured not to see real defects.",
"where": "workplan/2026-08/rajat_e2e_ci_audit_20260812.md"
"date": "2026-10-08",
"what": "GET /list-artifact/reverse/{geoid} answered the first hop of trace-forward for any hub login with no grant and no ledger entry; measured live, five of the six lots the gated route returns.",
"where": "agstack review ledger AG-015; agstack/ar2#19"
},
{
"date": "2026-08-19",
"what": "The phone column was removed from the drift guard's NOT NULL and UNIQUE lists so a change would pass, and the guard then certified an import that could not work.",
"where": "migration/tests/test_schema_drift.py"
"date": "2026-10-08",
"what": "AR2_INTERNAL_SHARED_SECRET opened GET /list-artifact/{id} and the trace-back walk with no credential and no ledger entry, while Pancake holding the same secret still demanded a credential and logged.",
"where": "agstack review ledger AG-076; agstack/ar2#20"
},
{
"date": "2026-10-08",
"what": "AR_ACCOUNT_L1=legacy turned any login into an L1 grant on four field routes; documented nowhere.",
"where": "app/routers/fetch_field.py at 318af8b; agstack/ar2#22"
},
{
"date": "2026-10-08",
"what": "ar2-hub minted the trace-forward capability from the role the user typed at registration.",
"where": "ar2-hub hub_main.generate_jwt at 9e53c35; agstack/ar2-hub#9"
}
],
"enforced_by": [
"app/tests/test_gate_inventory.py::test_every_door_to_a_gated_answer_carries_the_gate"
]
},
{
"recurrence_key": "prose-warning-instead-of-a-check",
"trigger": "You have identified a failure mode and are about to write it down in a document, review comment or email.",
"directive": "Write the check as well, in the same change, or record explicitly that it cannot be mechanized and why. A paragraph is read once by whoever was in the conversation and then archived; it does not bind the person who arrives next, and it does not bind its own author a week later.",
"kind": "mechanized",
"enforced_by": [
"harness/stomata/lessons.py",
"harness/stomata/checks.py",
"harness/tests/test_lessons.py"
],
"occurrences": [
{
"date": "2026-08-13",
Expand All @@ -231,49 +236,78 @@
"what": "The same document predicted that a check which becomes noise 'gets waived, then gets deleted'. Two checks became noise and were ignored for days by the author who wrote that sentence.",
"where": "workplan/2026-08/rajat_ar2_closeout_20260814.md:418"
}
],
"enforced_by": [
"harness/stomata/lessons.py",
"harness/stomata/checks.py",
"harness/tests/test_lessons.py"
]
},
{
"recurrence_key": "run-record-not-at-the-reviewed-commit",
"trigger": "You are reading a run record, CI result or packet to decide whether a branch is sound.",
"directive": "Check the commit the run was taken at against the tip being reviewed. Commits after the last recorded run are ungated, and that is exactly where an unnoticed regression sits.",
"kind": "advisory",
"advisory_because": "One occurrence so far, and the commit-binding check already records the commit for a reader to compare. Promoting on a single incident manufactures the kind of speculative check that turns into noise. Revisit on the next occurrence.",
"recurrence_key": "guard-weakened-to-pass",
"trigger": "A guard, assertion or lint rule stands between you and a change you believe is correct.",
"directive": "Change the claim or change the code, never quietly narrow the guard. If the guard is genuinely stale, say so in the commit message and re-record the baseline, so that weakening it costs a sentence someone can read.",
"kind": "mechanized",
"occurrences": [
{
"date": "2026-08-18",
"what": "Run records sat at b3c55ed and ac6e536 while the branch tip was 17d3aaf, and a lint regression rode in on the four ungated commits between.",
"where": "workplan/2026-08/rajat_import_review_20260818.md"
"date": "2026-08-12",
"what": "ruff.toml's ignore list was suppressing rule families including B008, so the linter was configured not to see real defects.",
"where": "workplan/2026-08/rajat_e2e_ci_audit_20260812.md"
},
{
"date": "2026-08-19",
"what": "The phone column was removed from the drift guard's NOT NULL and UNIQUE lists so a change would pass, and the guard then certified an import that could not work.",
"where": "migration/tests/test_schema_drift.py"
}
],
"enforced_by": [
"harness/stomata/checks.py",
"harness/tests/test_lessons.py::test_removing_an_assertion_from_a_guard_fails"
]
},
{
"recurrence_key": "ci-result-not-read-before-reporting",
"trigger": "You have pushed, and you are about to write that the change is green -- in a status log, a review, an email or a summary.",
"directive": "Read the run the push triggered before you write the word. A locally chosen subset of the suite is evidence about that subset; the repository's own gate is evidence about the change. If the run has not finished, say that it has not.",
"kind": "advisory",
"advisory_because": "The gate exists and worked: CI failed on 3aa3514 within two minutes and nothing in the repository was broken by the omission that the next run did not catch. What failed was the reporting step, which is outside this harness. Mechanizing it means a pre-push hook that blocks on a pending run, which would block legitimate pushes; revisit if this recurs.",
"occurrences": [
{
"date": "2026-09-15",
"what": "ar2 3aa3514 was pushed at 16:54 local and recorded in the workplan status log as shipped with '7 passed, 25 passed' from two hand-picked test files. Both CI workflows failed on that commit at 16:54 and 16:56, and the failure was found four hours later by reading the test rather than the run.",
"where": "confianza/ggms_workplan_20260915.md status log; AR2 CI 35037684619, stomata 35037684605"
}
]
],
"advisory_because": "The gate exists and worked: CI failed on 3aa3514 within two minutes and nothing in the repository was broken by the omission that the next run did not catch. What failed was the reporting step, which is outside this harness. Mechanizing it means a pre-push hook that blocks on a pending run, which would block legitimate pushes; revisit if this recurs."
},
{
"recurrence_key": "hashed-identifier-inverted-over-a-small-domain",
"kind": "mechanized",
"trigger": "You are about to call a hashed or derived identifier safe to publish, log, or hand to a counterparty, on the grounds that the hash is one-way.",
"directive": "One-way is a property of the function; disclosure is a property of the domain. Before publishing, count the inputs an attacker plausibly has to try and say the number out loud. A GeoID over a polygon's many-cell cover is safe because the attacker must guess a set. A GeoID over a point is digest([leaf.ToToken()]) with POINT_LEVEL = LEAF_LEVEL = 30 -- one cell of 0.78 cm2 -- so the domain is the leaf cells of whatever region the plot can be bounded to, and a farm is easy to bound. If that count is small, the identifier is the data in another form and inherits its masking rules, whatever the hash function guarantees.",
"kind": "mechanized",
"occurrences": [
{
"date": "2026-09-15",
"what": "A licence gate for third-party boundary ingestion was written in the agstack review workspace resting on the premise that 'a geoid is SHA-256 over sorted S2 cell tokens, it is one-way, publishing geoids alone discloses no geometry', and the plan it gated was to publish a geoid per boundary under CDLA-Permissive-2.0. The premise came from the AR 1.5 specification and doc/reference/AR_1.5_GEOID_COMPARISON.md, which describe one uniform algorithm over a compact cover and have no point case at all; ar2 app/geoid_v2.py has one. Reading it, the Geo Id returned by /register-point was inverted back to the exact registered coordinate from 6,489 candidate leaf cells in a box one metre across, in under two seconds. The same session's standalone run recovered a Lempira coordinate to 0.48 cm from 640,697 candidates at 121,701/sec single-threaded. Caught before any point geoid was published, and before the white paper proposing an open geoid corpus was written.",
"where": "agstack bin/check-boundary-license docstring as committed at 6084f0f; corrected at 9e246e9 and recorded as AG-034 in agstack review/findings.json, reproduction at agstack review/point_geoid_preimage.py"
}
],
"enforced_by": "app/tests/test_point_polygon_peers.py::test_a_points_geo_id_cannot_be_turned_back_into_its_coordinate, which registers through the API and brute-forces the Geo Id it returns. Read the next sentence before trusting the suite on this: the test is xfail(strict=True) and therefore does NOT block today, because the defect is real and unfixed pending the AG-034 identity decision. What strict buys is the other direction -- the moment a coarse published alias lands and the Geo Id stops inverting, the test passes, strict turns that pass into a build failure, and whoever did the work is told to take the marker off. Confirmed on 2026-09-15 in both directions: XFAIL as written, and '1 failed' when the assertion was neutralised."
"enforced_by": [
"app/tests/test_point_polygon_peers.py::test_a_points_geo_id_cannot_be_turned_back_into_its_coordinate, which registers through the API and brute-forces the Geo Id it returns. Read the next sentence before trusting the suite on this: the test is xfail(strict=True) and therefore does NOT block today, because the defect is real and unfixed pending the AG-034 identity decision. What strict buys is the other direction -- the moment a coarse published alias lands and the Geo Id stops inverting, the test passes, strict turns that pass into a build failure, and whoever did the work is told to take the marker off. Confirmed on 2026-09-15 in both directions: XFAIL as written, and '1 failed' when the assertion was neutralised."
]
},
{
"recurrence_key": "run-record-not-at-the-reviewed-commit",
"trigger": "You are reading a run record, CI result or packet to decide whether a branch is sound.",
"directive": "Check the commit the run was taken at against the tip being reviewed. Commits after the last recorded run are ungated, and that is exactly where an unnoticed regression sits.",
"kind": "advisory",
"occurrences": [
{
"date": "2026-08-18",
"what": "Run records sat at b3c55ed and ac6e536 while the branch tip was 17d3aaf, and a lint regression rode in on the four ungated commits between.",
"where": "workplan/2026-08/rajat_import_review_20260818.md"
}
],
"advisory_because": "One occurrence so far, and the commit-binding check already records the commit for a reader to compare. Promoting on a single incident manufactures the kind of speculative check that turns into noise. Revisit on the next occurrence."
}
]
}
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ ledger, not this block, or check L will fail on the next run.

*Enforced by: harness/stomata/packet.py, harness/tests/test_stomata.py*

### second-door-to-a-gated-answer (4 occurrences)

**When:** Adding a route, header, flag or claim that answers a question an existing route already gates, or reviewing one.

**Do:** Every door to a gated answer carries the same gate: enumerate the routes whose answer is gated and require each handler to call the gate and read no flag. A convenience path, a service header, an environment variable or a self-asserted claim that reaches the same data is a second key, and a second key nobody tracks is the one that gets used.

*Enforced by: app/tests/test_gate_inventory.py::test_every_door_to_a_gated_answer_carries_the_gate*

### prose-warning-instead-of-a-check (3 occurrences)

**When:** You have identified a failure mode and are about to write it down in a document, review comment or email.
Expand Down
Loading
Loading