Research: Event write hygiene

R-1 Public "known event type" predicate upstream

  • Decision: none is available. Core deletes its only caller of the private map instead (operator ruling).
  • Evidence:
  • At 11.2.1, spec_kitty_events.conformance.validators._EVENT_TYPE_TO_MODEL is still private, and upstream's own persistence.py:82 imports it.
  • The public validate_event raises ValueError for an unknown type.
  • In core, the only reader is _validate_lifecycle_payload. Its only caller is build_saas_lifecycle_queue_event, which has no src/ caller.
  • SignificanceEvaluated is persisted through the run journal (runtime/next/_internal_runtime/engine.py::_record_significance), never through this validator.
  • Alternatives considered:
  • The public validate_event with its ValueError: behaviour-identical, but it keeps a dead path alive.
  • A local explicit type set: duplicates about 92 names and freezes strictness.
  • Upstream proposal: export known_event_types() -> frozenset[str] from spec_kitty_events.conformance, and use it in upstream's own persistence.py. A second ask: public equivalents for the diary helpers that core's library-sync gate tests read (_apply_annotation_delta, _RUNTIME_SLOTS, _REPLACE_SLOTS, _wp_state_from_event).

R-2 Where the door bans live

  • Decision: extend the existing gates. See plan.md, Engineering Alignment.
  • Rationale: the charter's single canonical authority rule.
  • Alternatives considered: a new door-absence scanner. Rejected because it would duplicate the existing gates.

R-3 Atomic seeding semantics vs the deleted plain append

  • Decision: the helper delegates to store.append_events_atomic.
  • Differences, documented:
  • It adds a newline when the existing text lacks one; the old door concatenated.
  • It refuses a symlinked log.
  • It replaces the inode; the old door appended in place.
  • Handling: tests that pinned those quirks are rewritten to state the intended setup explicitly. See the census in traces/approach.md.

R-4 Seam rename blast radius

  • Decision: rename the seam and its residue: adapters.py identifiers and log labels, the emit._saas_fan_out helpers, the lifecycle _hosted fan-out, the test sink plugin and path-keyed ledgers. Keep no aliases.
  • Rationale: the operator ruling, and the fact that spec-kitty/event-router is dead.
  • Alternatives considered: renaming only in adapters.py. Rejected because it satisfies SC-003 on paper only.

Adversarial squad dispositions (post-spec)

FindingDisposition
New door gate duplicates existing gatesAccepted. The existing gates are extended (plan).
The private-import gate lives in test_events_tracker_public_imports.py, not test_shared_package_boundary.pyAccepted. That gate is widened, and G-18 is corrected.
A test allowlist is priced debtAccepted. The gate is limited to src/ with no allowlist, and an upstream ask is filed in the PR.
Rename scope too narrowAccepted. It covers the residue listed in R-4.
repo_root_for_lifecycle_log goes deadAccepted. It is deleted in IC-04.
ALLOWED_CALLERS does not shrink; FR-001 and FR-002 land togetherAccepted. They share a single work package.
The "byte-identical" claim needs a qualifier; the helper should batchAccepted (R-3).
ADR and doc factual updates; env var docs; breaking changelog entryAccepted (IC-06).
ops-index.jsonl is an index, not an event logAccepted. The wording is corrected in data-model.md.

Adversarial squad dispositions (post-tasks, reviewer lens)

FindingDisposition
kernel.atomic.notify_file_written goes dead with the door; the frontmatter test re-tests vacuouslyAccepted. WP02 T006 deletes it with its test and fixes the docstring. The frontmatter test moves from WP01 to WP02.
FR-002/FR-003 not red first; the dead-symbol gate is never redAccepted. The allowlist removals and the hasattr ban move into the T005 red commit.
FR-007 and the edge case promised a test allowlistAccepted. The spec is amended to the operator ruling (src/ only, no allowlist, upstream ask).
The identity spy would double-recordAccepted. The :311 line is always deleted.
Wrong references in WP02 / WP03 / WP04Accepted. Corrected (allowlist line refs, lifecycle line numbers, the 2-of-5 alias users, the test_coverage_breadth.py path).
Index census could over-match docstringsAccepted. It matches exact non-docstring constants, with a docstring control and _iter_records as the read-only control.
Baseline numbersAccepted. Expect 85→83 in widened_grandfathered_470, verified by running the gate.
Formatter-debt ratchet on renamed and door test filesAccepted. The ratchet test runs in WP02 and WP04, and --force-exclude is used.
Docs index drift from Amendment headings; changelog styleAccepted. docs_index.py --write is mandatory, the index is owned by WP05, and check_changelog_style runs.
Thread name and log text changeAccepted. Named as intended diffs in WP04.
Comment-level "SaaS" sweepAccepted. WP04 T017 adds a case-insensitive sweep over the seam's files.
FR-001 wording staleAccepted. The spec is reworded.