Skip to content

Prerequisite batch: relay tx (#257), Idempotency-Key bound (#258), RFC promotions (#249), governance files (#242), vendor-neutral AI gates (#252) - #259

Merged
choiyounggi merged 11 commits into
mainfrom
orch/prereq-1022
Oct 22, 2026
Merged

choiyounggi merged 11 commits into
mainfrom
orch/prereq-1022

Conversation

@choiyounggi

Copy link
Copy Markdown
Owner

Prerequisite batch from the 2026-10-11 enterprise audit (#255): five issues that must land before the v0.9.0 release (#245) and branch-protection reviews (#244). Run as one orchestration (7 tasks, each planned, reviewed and audited separately).

Closes #257
Closes #258
Closes #249
Closes #242
Closes #252

What changed

Issue Change
#257 lnpl relay runs each batch (drain → publish/POST → ack) inside one driver transaction. A row whose publish fails stays un-acked; the batch still commits the rows that were delivered. Rollback on ack failure, unexpected error or interrupt. docs/backends.md §3 states the delivery guarantee.
#258 An Idempotency-Key longer than 255 bytes is refused with 400 idempotency-key-too-long before any driver is opened. On a failed event consume, the open transaction is rolled back before idempotency_release; if that rollback fails, the claim is kept until TTL so no partial write is committed.
#249 RFC-0049 … RFC-0064 promoted to Accepted (implementation evidence checked per RFC), with RFC-0007 §2.2 Updated-by: back-links in the 15 target RFCs. scripts/rfc_lint.py now fails when CHANGELOG.md cites an RFC that is not Accepted or Superseded. README counts updated.
#242 SECURITY.md (GitHub private vulnerability reporting, now enabled), CONTRIBUTING.md, CODE_OF_CONDUCT.md (Contributor Covenant 2.1), .github/CODEOWNERS.
#252 _ai-gate.yml gains a runner input; each AI gate gets a generic-http job (OpenAI-compatible chat completions) that runs only when vars.AI_GATE_HTTP_BASE_URL is set. The claude-code path is unchanged.

Verification

  • Full suite on the merged branch: Ran 5530 tests / OK (skipped=1) (5394 at the base commit).
  • rfc_lint, gen_plugin_references --check, check_version_sync, check_doc_snippets: rc 0. ruff check impl/lnpl scripts: clean.
  • Every task passed a fresh-context task review; R2 tasks also passed a mutation-based test-quality audit. An integration review of the whole diff approved it.

Known gaps (filed as follow-up issues)

  • generic-http: schema-invalid model output turns the advisory check red; prose-factcheck does not see the PR title/body; docs/CI-GATES.md roster row for rfc_lint does not mention the new CHANGELOG rule.
  • Postgres: the consume route's CloudEvents id is not length-bounded; a DriverError from idempotency_release still escapes as a raw 500; the OpenAPI Idempotency-Key header has no maxLength; relay batches are unbounded.
  • RFCs: rfc_lint does not check §2.2 back-links; §2.2 rule-7 consolidation candidates.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Th13jVHptQQ1mCLah7XBay

choiyounggi and others added 11 commits October 22, 2026 12:29
…EOWNERS (refs #242)

- SECURITY.md: report through GitHub private vulnerability reporting
  (Security tab, "Report a vulnerability"); latest release supported per
  docs/compatibility.md; acknowledge within 3 days, fix or coordinated
  disclosure within 90 days, disclosure as a GitHub Security Advisory.
- CODE_OF_CONDUCT.md: Contributor Covenant 2.1, enforcement contact is the
  private report form or the maintainer; no email address.
- CONTRIBUTING.md: dev_doctor -> test suite -> RFC process -> PR checklist,
  including the deterministic and AI gates CI runs.
- .github/CODEOWNERS: catch-all first, then impl/lnpl/, rfcs/, .github/,
  all @choiyounggi (prerequisite for code-owner review in #244).
- impl/tests/test_governance_files.py: the four files exist; CODEOWNERS
  covers the required paths, names only the maintainer, points at existing
  paths and keeps the catch-all first; parser rejects a missing or non-@
  owner and skips comments and blank lines.
- impl/tests/mutation_check.py: TREE_CONTENTS copies the three new root
  files so the mutation harness tree matches what the tests read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qyv3iqsmLFDfctQ6MVdQ8T
`lnpl relay` drained the outbox outside any transaction, so on
lnpl-postgres (SELECT ... FOR UPDATE SKIP LOCKED) the row locks ended
with the statement and two relays could publish the same row. Both relay
loops now route each batch (drain -> publish/POST -> ack) through a new
`_relay_batch` helper: begin() before the drain, commit() after the
batch, rollback() when an exception escapes it (ack failure, unexpected
publisher error, KeyboardInterrupt). A row whose publish fails stays
un-acked and the batch still commits. `_relay_drain_once*`, the rc-2
drain-contract guard and `outbox drain`/`outbox ack` are unchanged.

docs/backends.md §3 states the delivery guarantee: exactly-once per
batch on row-locking drivers, at-least-once across unwrapped drains and
end to end, unchanged on sqlite; the batch stays unbounded (follow-up).

Tests: RelayBatchTransactionTest (9) pins the call order per batch.
Full suite 5404 -> 5413, OK (skipped=1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgjbKPS63sN3T9ANagQdy1
…asing a consume claim (#258)

An Idempotency-Key longer than 255 header bytes now gets 400
idempotency-key-too-long (problem+json, M23) in _do_post, before any
repository driver is opened, on every backend. Before this, a key of
about 4000 characters overflowed lnpl-postgres's btree index on
lnpl_idempotency (workflow_id, key) and the request failed with an
unhandled 500. The new code is added to wsgi._TITLES and
lexer.RESERVED_PROBLEM_CODES.

On POST /-/events/<slug>, when the workflow raises an exception that
run_workflow does not catch, the open workflow transaction is now rolled
back before idempotency_release. The key is free at once and the failed
run's partial writes are not committed with the release. If the rollback
itself fails, the claim is kept until its TTL.

docs/serving.md documents the bound and the D6 release order. The old
test that accepted a 4096-character key now asserts the 400 instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHmmWQ9U1LKAF5fPdc7DUH
…ase on consume failure (refs #258)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Th13jVHptQQ1mCLah7XBay

# Conflicts:
#	CHANGELOG.md
Promote the 16 CHANGELOG-cited Draft RFCs after per-RFC implementation
evidence (CHANGELOG line + citing test module, no baseline failures;
RFC-0053 phase-2 sites present). Status lines read
`**Accepted** (RFC-00NN, 2026-10-22)`.

- Status sections of 13 RFCs keep their Draft sentence and gain a dated
  resolution note (R-t249-6).
- Updates lines of 0051–0054 name the predecessor updater per RFC-0007
  §2.2 rule 5 (nine `(+RFC-NNNN)` notes, R-t249-7/7a).
- README.md / README.ko.md: RFC count lines (62 Accepted, 2 Draft) and
  the 0049–0064 table cells drop the Draft marker.
- kb/cloud/cloud-event-delivery.md: RFC-0061 cited as Accepted.

Back-links in target RFCs follow in t249a2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvGUpaxKn39SLd9eVtUNge
scripts/rfc_lint.py now scans CHANGELOG.md for `RFC-NNNN` citations,
including the slash form `RFC-NNNN/NNNN`. A cited RFC must have a
document under rfcs/ (else a §3 finding) and be Accepted or Superseded
(else a §2.1 finding naming the number and status). A missing
CHANGELOG.md is its own §2.1 finding; a malformed rfcs/ file name stays
a §3 finding through main().

18 new tests (47 in test_rfc_lint). Full suite 5438 -> 5456 OK
(skipped=1). One CHANGELOG bullet under Unreleased / Changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvKDLFWETgQF2SpyPYffgz
…s (refs #249)

RFC-0049–RFC-0063 are Accepted, so every target their `- Updates:` lines
name now carries the matching back-links, inserted only:

- 35 `- Updated-by: RFC-NNNN (§…)` lines at the end of the Status list
  of 15 target RFCs (0001 0002 0003 0012 0014 0015 0016 0025 0027 0028
  0030 0038 0040 0044 0057)
- 51 `> 갱신됨: RFC-NNNN` pointers at the head of each named section,
  appended to any existing pointer block (rule 5), including the
  RFC-0044 §Open Questions 1 pointer RFC-0051 promised at promotion

The (updater, target) pairs re-derived from the merged Updates lines
match: 35 pairs, none missing. rfc_lint unchanged (Accepted 62, Draft 2,
Superseded 1, no problems); full suite 5438 OK (skipped=1) before and
after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X1JyTUkjyMucgjR46y7hRR
The reusable _ai-gate.yml takes a `runner` input (`claude-code` by
default, unchanged; or `generic-http`). scripts/ai_gate_http_runner.py
(stdlib only) sends the gate prompt and the PR diff to any
OpenAI-compatible /chat/completions endpoint and writes the same
verdict JSON that scripts/ai_gate_verdict.py checks. Every failure is a
named `error: ...` line, the key is masked on every output channel, and
a key containing whitespace is rejected before the request.

Each AI gate gets an advisory `<gate>-generic-http` caller job that runs
only when the repo variable AI_GATE_HTTP_BASE_URL is set, and skips like
the Anthropic gates when the AI_GATE_HTTP_API_KEY secret is missing. The
four existing jobs are byte-identical. docs/CI-GATES.md documents both
runners and how to register generic-http.

Tests: impl/tests/test_ai_gate_http_runner.py (30, against a local fake
server) and impl/tests/test_ai_gate_workflow.py (44, which run the
changed run: blocks under bash -e and LANG=C.UTF-8 and pin the wiring).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DNNdNyUJL7gGcojHmehAYi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment