Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
---
id: B-0088
priority: P2
status: open
title: memory/MEMORY.md paired-edit lint is advisory only (not in required-status-checks); promote or remove the discoverability claim
tier: factory-tooling
effort: S
ask: autonomous-loop tick discovery 2026-04-28T20:23Z (paired-edit failures observed on PR #688/#689; both auto-merged anyway)
created: 2026-04-28
last_updated: 2026-04-28
composes_with:
- B-0087
tags: [otto-2026-04-28, github-actions, branch-protection, advisory-vs-enforcement, factory-hygiene, memory-index-integrity]
---

# B-0088 — paired-edit lint is advisory; either promote to required or weaken its claim

## Discovery

While fixing PR #688/#689's paired-edit lint failures (each
PR added `memory/CURRENT-{aaron,amara}.md` without the
required `memory/MEMORY.md` paired touch), observed that
**both PRs merged anyway via auto-merge** despite the lint's
explicit `exit 1` failure.

PR #689 merged before I could even push the marker-bump fix.
PR #688 is still in CI (post-fix) but the lint failure on
its earlier commit didn't block the auto-merge arming —
both are arming/firing on the green-required-checks set,
not the all-checks-green set.

## The bug

Branch protection + ruleset configuration audit:

```bash
$ gh api repos/Lucent-Financial-Group/Zeta/branches/main/protection \
--jq '.required_status_checks.contexts'
[
"lint (semgrep)",
"lint (shellcheck)",
"lint (actionlint)",
"lint (markdownlint)",
"build-and-test (macos-26)",
"build-and-test (ubuntu-24.04)",
"build-and-test (ubuntu-24.04-arm)"
]

$ gh api repos/Lucent-Financial-Group/Zeta/rulesets/15256879 \
--jq '.rules[] | select(.type=="required_status_checks")'
(empty)
```

The job `check memory/MEMORY.md paired edit` (from
`.github/workflows/memory-index-integrity.yml`) is NOT in
either required-checks list.

## Failure mode

The workflow:

1. Runs on every PR with `memory/*.md` changes.
2. Detects when memory file is touched without paired
MEMORY.md touch.
3. Exits 1 with a clear error message including the rule
("Fresh sessions read MEMORY.md at cold start; a memory
landed without a pointer is undiscoverable. See NSA-001
in docs/hygiene-history/nsa-test-history.md for the
canonical incident this check prevents.")
4. **Auto-merge fires anyway** because the failed check
isn't gating.

The discoverability claim in the lint message ("memory landed
without a pointer is undiscoverable") is **functionally
false** under current configuration: memories CAN land without
pointers and merge to main without obstruction.

## What this row asks the maintainer

Two options:

**A — promote the lint to a required status check.** Add
`check memory/MEMORY.md paired edit` to the
`required_status_checks.contexts` list in branch protection
(or the ruleset). Makes the discoverability claim true.

**B — weaken the lint's message + accept advisory mode.**
If the lint is intentionally advisory (because some PRs
legitimately need to skip the pairing — e.g. CURRENT-* file
edits where MEMORY.md doesn't change semantically), then
update the lint's error message to match: "advisory check;
review whether MEMORY.md should be touched". Don't claim
discoverability protection that isn't enforced.

## Why P2 (not P0/P1)

- The discoverability gap is real but slow-growing — a
memory file landing without an index pointer is
recoverable later (just add the row).
- The visibility-constraint rule (Aaron 2026-04-28) limits
autonomous branch-protection edits, so this needs
maintainer action.
- The factory hygiene workflow at task #269 (cadenced
counterweight-audit skill) covers detect+repair on this
class — manual recovery is cheap.

## Counterweight-taxonomy classification

This is an **Advisory Enforcement Workflow Gap** class
instance (Amara 2026-04-28T20:24Z formal class name; covered
in `memory/feedback_advisory_enforcement_workflow_gap_amara_class_name_otto_2026_04_28.md`).

> **Definition (Amara verbatim):** A workflow claims or
> implies enforcement, but is not included in the
> required-status-checks set, so failures are observable but
> non-blocking.

The failure mode is:

- Cheap prevention layer (lint job) exists.
- Cheap prevention layer fires correctly (exit 1 on
violation).
- BUT cheap prevention layer is not wired into the
enforcement gate (required-status-checks).
- Result: the prevention is observable but not
actionable.

The fix shape: every lint workflow with a clear "this
prevents incident X" claim should be either (a) a required
check OR (b) have a less-strong claim that matches its
actual gate-status.

## Sibling lints to audit

Walking `.github/workflows/` for advisory-vs-required
parity:

- `memory-index-integrity.yml` — this row's incident
- `memory-reference-existence-lint.yml` — likely same shape
- `memory-index-duplicate-lint.yml` — likely same shape
- `backlog-index-integrity.yml` — likely same shape
- `github-settings-drift.yml` — already broken (B-0087)

Each lint should be checked: is the claim it makes about
preventing X actually backed by enforcement? File
follow-up rows for any that fail the same audit.

## Acceptance criteria

- [ ] Maintainer picks A (promote to required) or B
(weaken claim).
- [ ] If A: lint is added to `required_status_checks.contexts`
via branch protection (or ruleset).
- [ ] If B: lint's error message updated to acknowledge
advisory status.
- [ ] Sibling lint audit completed (memory-reference,
memory-index-duplicate, backlog-index-integrity).

## Composes with

- B-0087 (github-settings-drift broken — same surface, same
visibility-constraint deferral)
- `memory/feedback_aaron_visibility_constraint_no_changes_he_cant_see_2026_04_28.md`
— branch protection edits need maintainer pre-approval
- `memory/feedback_incomplete_source_set_regeneration_hazard_and_workflow_null_result_audit_amara_2026_04_28.md`
— different class (this isn't null-result, it's
unenforced-result), but same task #269 audit family

## Operational note for future-Otto

When a lint job fires `exit 1` with a discoverability /
correctness claim, verify the claim is enforced by checking
that the job name appears in
`required_status_checks.contexts` OR the relevant ruleset.
If not, the claim is overstated; either fix the gate or
weaken the message. Don't trust claim text — verify the
gate.
4 changes: 3 additions & 1 deletion memory/MEMORY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[AutoDream last run: 2026-04-23]

**📌 Fast path: read `CURRENT-aaron.md` and `CURRENT-amara.md` first.** <!-- paired-edit: PR #685 typescript-default + chronological-polarity-error 2026-04-28 --> These per-maintainer distillations show what's currently in force. Raw memories below are the history; CURRENT files are the projection. (`CURRENT-aaron.md` refreshed 2026-04-28 with sections 26-29 — speculation rule + EVIDENCE-BASED labeling + JVM preference + dependency honesty + threading lineage Albahari/Toub/Fowler.)
**📌 Fast path: read `CURRENT-aaron.md` and `CURRENT-amara.md` first.** <!-- paired-edit: PR #691 advisory-enforcement-gap + class-naming-ferry-protocol-with-sd9 2026-04-28 --> These per-maintainer distillations show what's currently in force. Raw memories below are the history; CURRENT files are the projection. (`CURRENT-aaron.md` refreshed 2026-04-28 with sections 26-29 — speculation rule + EVIDENCE-BASED labeling + JVM preference + dependency honesty + threading lineage Albahari/Toub/Fowler.)

- [**Class-Naming Ferry Protocol + SD-9 guardrail (Amara 2026-04-28; Aaron reinforced)**](feedback_class_naming_ferry_protocol_with_sd9_guardrail_amara_2026_04_28.md) — Meta-class for the Otto→Aaron→Amara→encode genre. SD-9 guardrail LOAD-BEARING: Amara endorsement is signal, not proof. Local factory-hygiene classes encode freely; non-local claims need substrate evidence + external lineage + falsifier. Anti-pattern: "Amara blesses the name, therefore true."
- [**Advisory Enforcement Workflow Gap — class name (Amara 2026-04-28); decision-fork (B-0088 instance)**](feedback_advisory_enforcement_workflow_gap_amara_class_name_otto_2026_04_28.md) — Workflow claims/implies enforcement but is not in required-status-checks set; failures observable but non-blocking. Worked example: paired-edit lint failed on PR #688/#689 but both auto-merged. Decision: promote to required OR downgrade claim to advisory. Risk: factory believes rule enforced when only logged. "Guardrail that looks like enforcement but behaves like telemetry."
- [**Incomplete Source-Set Regeneration Hazard + Workflow Null-Result Audit Signal — Amara class names + controls (2026-04-28)**](feedback_incomplete_source_set_regeneration_hazard_and_workflow_null_result_audit_amara_2026_04_28.md) — Two reusable classes: (1) "regenerate from sources" tools become destructive when source-set is incomplete; control is `--check` / `--stdout` first, force-write only after completeness proven. (2) `gh run list []` on existing workflow is audit signal not conclusion; six diagnostic questions (too-new / disabled / non-default-branch / cron / event-trigger / identifier-filter). Both fold into task #269.
- [**Chronological Insertion Polarity Error — class name + append-only-on-oldest-first discipline (Amara 2026-04-28)**](feedback_chronological_insertion_polarity_error_amara_class_name_otto_2026_04_28.md) — Edit-tool prepend on oldest-first append-only files = chronological reversal. Discipline: `cat >> file <<EOF` always-append OR run canonical-sort post-edit. Lint hook caught Otto's PR #684 incident in 1min. Mechanism-over-vigilance.
- [**TypeScript/Bun is the default; step out on TypeScript carefully (Aaron 2026-04-28)**](feedback_typescript_bun_default_step_out_carefully_aaron_2026_04_28.md) — Factory tooling default is TypeScript on Bun (`bun@1.3.13`). Step-out threshold: AI/ML primary library availability. New tooling defaults to TS; existing Python ports when changes are substantive (B-0086 catalogues port candidates).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
---
name: Advisory Enforcement Workflow Gap — class name + decision-fork (Amara naming, Otto incident, 2026-04-28)
description: Amara 2026-04-28T20:24Z named the class after Otto observed paired-edit lint failures merging through on PR #688/#689. Definition — workflow claims/implies enforcement but is not in required-status-checks set; failures are observable but non-blocking. Decision fork — promote to required check OR downgrade enforcement claim to advisory. Risk — factory believes rule is enforced when it's only logged. Worked example: memory-index-integrity.yml lint claims "memory landed without a pointer is undiscoverable" but PRs #688/#689 both failed the lint and auto-merged anyway.
type: feedback
---

# Advisory Enforcement Workflow Gap

## Class name (Amara 2026-04-28T20:24Z)

**Advisory Enforcement Workflow Gap** — Amara formalized the
class after Otto observed paired-edit lint failures merging
through on PR #688/#689 despite the lint's `exit 1` and the
explicit "this prevents incident X" claim in its error
message.

## Definition (Amara verbatim)

> A workflow claims or implies enforcement, but is not included
> in the required-status-checks set, so failures are observable
> but non-blocking.

## Concrete incident (Otto 2026-04-28T20:23Z)

- Workflow: `.github/workflows/memory-index-integrity.yml`,
job `check memory/MEMORY.md paired edit`.
- Claim in error message: *"Fresh sessions read MEMORY.md at
cold start; a memory landed without a pointer is
undiscoverable. See NSA-001 in
docs/hygiene-history/nsa-test-history.md for the canonical
incident this check prevents."*
- Reality: NOT in `required_status_checks.contexts` of branch
protection AND not in any ruleset's required-checks list.
Verified via `gh api repos/.../branches/main/protection
--jq '.required_status_checks.contexts'`.
- Observed: PR #688 + #689 both failed the lint, both
auto-merged anyway.
- Filed as: B-0088.

## Risk (Amara framing)

> The factory believes a rule is enforced when it is only
> logged.

A workflow that fires `exit 1` with a clear discoverability /
correctness claim looks like enforcement to anyone reading the
code. Discovering it isn't is a failure mode that compounds:
each "enforced" rule that's actually advisory adds a hidden
gap between the documented contract and the actual
enforcement.

## Decision fork (the control)

When you find an Advisory Enforcement Workflow Gap, pick one:

**Option A — promote to required status check.**

```bash
# Add the lint job's check name to required-status-checks
gh api repos/<OWNER>/<REPO>/branches/main/protection \
--method PATCH \
--input <(gh api repos/<OWNER>/<REPO>/branches/main/protection \
| jq '.required_status_checks.contexts += ["check memory/MEMORY.md paired edit"]')
```

(Or update via ruleset API if the repo uses rulesets.)

This makes the discoverability / correctness claim TRUE.
Failures now block merge.

**Option B — downgrade the claim to advisory visibility.**

If the lint is intentionally advisory (because some PRs
legitimately need to skip the rule — e.g. CURRENT-* file
edits where MEMORY.md doesn't change semantically), update
the lint's error message to match:

```text
ADVISORY: this check is informational only; review whether
MEMORY.md should be touched. Merge gating does NOT depend on
this check.
```

Don't claim enforcement that isn't enforced.

## Why both options are valid

The factory may legitimately want some lints advisory:

- **Always-required (option A)**: lints that catch real
correctness bugs (broken-cross-references, schema
violations, syntax errors).
- **Advisory (option B)**: lints that catch *style* or
*convention* drift where the maintainer wants signal but
not gate (e.g. "consider adding pointer entry"; "you
might want to update CURRENT-aaron.md too").

The ANTI-PATTERN is claiming enforcement while running
advisory.

## How to audit for this class

Walk every workflow in `.github/workflows/`:

1. Read the workflow's failure message / claim text.
2. Identify the check job's name (the YAML `jobs.<id>.name`
field).
3. Query `gh api repos/.../branches/main/protection
--jq '.required_status_checks.contexts'` and the same
for any rulesets.
4. For each check that **claims** enforcement: is its name in
the required list?
5. For each check that's NOT in the required list: does its
message claim enforcement?
6. Mismatch = Advisory Enforcement Workflow Gap.

This audit fits as a tier-2 cadenced-counterweight check
(task #269), running monthly or on substantive workflow
changes.

## The dangerous middle state (Amara's framing)

> A guardrail that looks like enforcement but behaves like
> telemetry.

This is the middle state between "enforced rule" and
"advisory hint". From the outside, the factory looks
defended; in fact, the rule is bypassable. Worse than
explicit advisory mode: explicit advisory is honest;
implicit advisory pretends.

## Sibling lints to audit (Otto 2026-04-28)

In Zeta's `.github/workflows/`, candidates for the same audit:

- `memory-index-integrity.yml` (this incident)
- `memory-reference-existence-lint.yml` (likely same shape)
- `memory-index-duplicate-lint.yml` (likely same shape)
- `backlog-index-integrity.yml` (likely same shape)
- `github-settings-drift.yml` (already broken — B-0087;
separate failure mode, "workflow startup error", but also
not in required list either way)

Each should be audited per the procedure above.

## Composes with

- B-0088 (the concrete-instance backlog row this class names)
- B-0087 (github-settings-drift — different failure mode but
same audit family)
- `memory/feedback_incomplete_source_set_regeneration_hazard_and_workflow_null_result_audit_amara_2026_04_28.md`
— Workflow Null-Result Audit + Scheduled Workflow
Null-Result Hygiene Scan classes; this is a sibling
audit class (different failure shape: "fires but doesn't
enforce" vs "exists but doesn't fire")
- `memory/feedback_aaron_visibility_constraint_no_changes_he_cant_see_2026_04_28.md`
— branch-protection edits need maintainer pre-approval;
this audit produces backlog rows, not autonomous fixes
- Task #269 — cadenced-counterweight-audit skill should
add this audit to its tier-2 catalogue

## What this is NOT

- **NOT a directive to promote every advisory lint.** Some
lints are intentionally advisory; the discipline is
message-truth, not always-required.
- **NOT a license to skip audits.** Even on advisory lints,
the failure mode of failing-without-noticing is real;
reviewers should still address advisory failures.
- **NOT specific to GitHub Actions.** The same gap exists in
any CI system (GitLab CI rules with `allow_failure: true`,
Jenkins post-build steps that report but don't fail the
build, etc.).

## Pickup notes for future-Otto

When you write a new lint workflow:

1. Decide explicitly: required or advisory?
2. If required: add the check name to
`required_status_checks.contexts` in the same PR.
3. If advisory: write the failure message in advisory tone
("review whether...", "consider whether...", not
"this prevents incident X").
4. Don't lie about which kind it is.

When you encounter a failing lint that's not in the required
list:

1. Don't assume the failure will block.
2. If you see the check fail and the PR auto-merge anyway,
that's the gap; file an Advisory Enforcement Workflow Gap
row.
Loading
Loading