Skip to content

feat(store): carry the store-privilege preflight onto current main (clears BACKLOG #1234) - #764

Merged
wshallwshall merged 13 commits into
mainfrom
feat/store-privilege-preflight-carry
Sep 9, 2026
Merged

feat(store): carry the store-privilege preflight onto current main (clears BACKLOG #1234)#764
wshallwshall merged 13 commits into
mainfrom
feat/store-privilege-preflight-carry

Conversation

@wshallwshall

@wshallwshall wshallwshall commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

What this is

A clearing act, not a bug fix. BACKLOG #1234 reports a real defect in the store-principal privilege preflight, and no Builder could act on it, because the subject does not exist on main. This carries the code onto current main so it becomes ordinary reviewable code.

Measured 2026-09-03 at origin/main:

Probe Result
require_least_privilege in any .py zero hits
least_privilege in any .py zero hits
Positive control, same instrument and ref: require_managed_identity hits across four .py files

So the zero is a real absence. The code lived only on w3-store-privilege-preflight, tip 94cb72e6, dated 2026-08-11, 4 ahead of and 530 behind origin/main, with no pull request ever opened for it.

w3-store-privilege-preflight is untouched. This is a new branch cut from current origin/main with the commits cherry-picked onto it. The original branch is the only copy of that work and it stays exactly as it was. It was not rebased in place and not force-pushed.

Commits carried, and the one dropped

Original Action Reason
11b0a8aa preflight, 18 files carried The feature.
d12b28de corrections, 8 files carried postgres_excess wrapper-role fix, per-backend live-DB CI steps, seam bump.
ffe1afee docs, 1 file carried Links the two findings that read like probe misfires.
94cb72e6 record dropped Empty commit. It changes no file and exists only to correct d12b28de's message, which claimed a live lane was dead. A stale coordination record about lane liveness has no value on main 530 commits later. Its correction is applied to the carried message instead.

Commit messages were rewritten on carry, and that is a deliberate call. 11b0a8aa read RESCUE COMMIT, DO NOT MERGE AS-IS, and recorded a --no-verify commit. d12b28de read NOT MY WORK, UNVERIFIED, DO NOT LAND THIS, and 94cb72e6 exists precisely because all four of those claims were false. Carrying that text onto main would import a false instruction that outlives the branch it described. The file content is unchanged; only the messages differ, and each rewritten message names the commit it came from.

ffe1afee's original message carried a verification record taken against a 2026-08-11 baseline. It was dropped rather than repeated, because re-asserting a 530-commit-old run as if it covered this tree would be false. What actually ran is below.

Three commits on top are mine, not carried: the BACKLOG amendment, a CHANGELOG correction, and a CI path-gate fix. Both corrections are explained below.

Conflicts, all resolved toward current main

1. .github/workflows/ci.yml, the server-DB path gate. Main's regex is far broader than the branch's. Kept main's and added the branch's one real addition, store_privilege, to the tests/test_(...) alternation. The messagefoundry/store/ prefix already pulls the server-DB legs for privilege.py; the test-file arm matters when only the test changes.

2, 3, 4. The seam trio. The branch hand-bumped ENGINE_UI_SEAM to 19. Main retired the hand-chosen integer for a discovered digest under BACKLOG #1220, and main's own seam comment names this branch as half of the reason it did: two unlanded branches both bumped to 19 for independent contract changes. Took main's scheme and regenerated with scripts/webconsole_seam_snapshot.py --write.

  • ENGINE_UI_SEAM moves 93ba1f10b9dccfc8 to b93f38d097f97a45.
  • The golden gains exactly two lines: store_privilege on SecurityPosture, and a StorePrivilegeView: detail, excess, status row.
  • messagefoundry_webconsole.SUPPORTED_ENGINE_SEAMS set by hand in the same commit, which the tool requires by design so the handshake does not become a self-consistent tautology.
  • The branch's v19 seam comment is dropped. Its content, the four-value status semantics, already lives on the model in api/models.py.

Two things the carry broke that the repo's own gates caught

tests/test_serverdb_ci_coverage.py went red, and it was right to. The carried test file is a server-DB suite and imports messagefoundry.api; main's regex covered messagefoundry/api/app but not the package __init__. So editing that file would not have pulled the legs that prove it. Widened to messagefoundry/api/(__init__|app). That gate binds the asserted-on file rather than the asserting one, which is exactly why a new suite can open a hole in it without touching the source the hole is about.

The carried CHANGELOG asserted a seam bump 18 to 19. True on the 2026-08-11 branch, false here. Corrected to the digest move. A changelog line naming a version that does not exist is a false record a reader has no way to catch.

The #1234 defect is still here, deliberately

Not fixed in this PR. #1234 wants a two-arm test, red against a fixture returning NULL role rows and green on a genuine least-privilege principal, and that is its own reviewable change. Mixing a behaviour fix into a 530-commit catch-up makes both unreviewable.

For the next Builder, in messagefoundry/store/sqlserver.py, inside probe_principal_privileges at :2917:

Line The defect
:2980 status=StorePrivilegeStatus.OBSERVED returns unconditionally once row is not None.
:2954, :2957 row[f"srv_{i}"] == 1 and row[f"dbr_{i}"] == 1 fold a NULL to False, which reads as "not a member", which reads as clean.
:2988, :2989 row["control_server"] == 1 and row["control_db"] == 1 fold NULL the same way.

The :2988/:2989 limb is not in #1234's original scope, which named the role columns only. A fix reading just that scope would leave a direct GRANT CONTROL SERVER mis-read exactly as before. Recorded on the item as well.

Sibling arms for comparison: postgres.py:1245, store.py:4672.

An adjacent observation, offered as a question rather than a claim. The Postgres arm at postgres.py:1292 also returns OBSERVED unconditionally once its scalar row is non-NULL, and folds its columns through bool(...). Its exposure is genuinely different, because pg_roles is world-readable and needs no special permission, so the SQL Server visibility problem does not transfer directly. But pg_has_role(current_user, r.oid, 'MEMBER') is true of the principal's own role by construction, so an empty rows result is not reachable for a valid principal, and the code would report it as observed with no roles. Whether that is the same defect or merely the same shape is for whoever fixes #1234 to decide. I did not measure it and am not filing it.

Everywhere else the module handles this correctly and says so in its own words: __main__.py passes None for the not-yet-open store and comments that None here is "not yet observed", never "observed and clean". That is what makes the SQL Server arm stand out.

[store].require_least_privilege stays False, exactly as the branch had it, at config/settings.py:569. Flipping a security default was not in scope.

A post-merge consequence that is not a Builder's call

messagefoundry/store/privilege.py names IS_SRVROLEMEMBER, IS_ROLEMEMBER, db_owner and sysadmin, and it lands inside messagefoundry/, which scripts/asvs/scorecard.py::_python_sources scans. That flips ASVS cell 13.2.2's absence claim (pattern = "IS_SRVROLEMEMBER|IS_ROLEMEMBER|db_owner|sysadmin") to false. The module's own docstring says so and says the paired vault-side re-score is a separate deliberate act.

Nothing goes red on this PR from it: asvs-prove-absences.yml fires only on pull_request paths touching its own wiring, which this PR does not touch, and the scheduled pass runs in the vault. So this surfaces after merge, on the vault's own schedule, and the re-score belongs to whoever holds that record. Flagging it rather than leaving it to be discovered.

Checks

Check Result
ruff check . All checks passed
ruff format --check . 1222 files already formatted
mypy messagefoundry (strict) Success, no issues in 268 source files
Targeted pytest, 7 files 199 passed, 5 skipped
Gate tests, 6 files 42 passed after the path-gate fix
scripts/docs/backlog_status_check.py OK, 657 items, each declaring exactly one status
scripts/docs/dangling_citation_check.py exit 0; the 11 flagged numbers are all pre-existing, none cited by this PR
Full suite, locally 1 failed, 4147 passed, 169 skipped — see the next section

Targeted run covered test_store_privilege_preflight, test_webconsole_seam_snapshot, test_security_posture_defaults, test_security_config, test_alert_smtp_tls, test_client_network_allowlist, test_memory_encryption_readout. Gate run covered test_serverdb_ci_coverage, test_docs_db_grants, test_asvs_prove_absences_wiring, test_required_contexts, test_backlog_status_check and test_dangling_citation_check.

A local test failed, and here is why I concluded it was the box and not this change

Not buried, because a failure I explain away is exactly the kind of claim that should be easy for you to re-check.

FAILED tests/test_connscale_smoke.py::test_no_accept_acked_message_is_absent_from_the_stopped_engines_store
1 failed, 4147 passed, 169 skipped in 1264.82s

The run used -x, so 4147 is a prefix and not a whole-suite figure. Three readings, and the two that could have contradicted the conclusion did not:

  1. It failed once, on a box that was at that moment running three other sessions' pytest suites concurrently. connscale is a connection-scale smoke test asserting on engine stop and store state, which is the test shape most sensitive to CPU starvation.
  2. It passes locally in isolation on this branch, once the box was quiet: tests/test_connscale_smoke.py 11 passed in 22.89s, including the failing test by name.
  3. It passes in CI on this exact head, on three platforms. test (ubuntu-latest, py3.14) reports 11515 passed, 847 skipped and uploads a connscale-readings artifact, so connscale ran there rather than being skipped.

Reading 2 or reading 3 could each have come back red and did not. I am reporting it as environmental contention rather than a defect, but it is a judgement, not a measurement — if you would rather see the local full suite run clean end to end on a quiet box before merging, that is a reasonable thing to ask for and I did not do it.

security_loosenings() gained a positional parameter in this carry. Every caller outside messagefoundry/ is in tests/, all five such files were updated by the carry, and all five pass. Nothing in harness/, tee/ or scripts/ calls it.

The legs I said nobody could see have now run, and they passed

Run 33803284381 at head a4225b017. I checked the step logs rather than the job conclusions, because a pytest step in which every live test skips also exits 0, and a green job would not have told them apart.

Job Live legs Step result
sql server (store + connector) 2022 test_live_sqlserver_probe_observes_the_configured_principal PASSED, ..._sees_both_directions_on_a_purpose_made_principal PASSED 46 passed, 2 skipped (the 2 skips are the Postgres legs, correctly)
postgres store test_live_postgres_probe_observes_the_configured_principal PASSED, ..._sees_both_directions_on_a_purpose_made_role PASSED 46 passed, 2 skipped (the 2 skips are the SQL Server legs, correctly)

So all four legs that skip on a developer box have executed against real servers, and each pair skipped in the other backend's job exactly as intended. sql server (store + connector) 2025 also passed.

detect server-DB + docker changes passed and the server-DB jobs were created, which is the direct evidence that the path-gate resolution works.

Also green on this head: test (ubuntu-latest, py3.14), test (windows-2022, py3.14), test (windows-2025, py3.14), all three web console tests, both ide build legs, repo harness tests on both platforms, and a PR that implements BACKLOG #N must update BACKLOG.md. Thirty checks passing, eight skipping, zero failing.

Still pending at the time of writing: CI gate, crypto-inventory, pip-audit, and three advisory legs. CI gate is the one that rolls the server-DB legs up, so it is still the check to read before merge — the individual greens above are not a substitute for it.

security_loosenings() gained a positional parameter in this carry. Every caller outside messagefoundry/ is in tests/, all five such files were updated by the carry, and all five pass. Nothing in harness/, tee/ or scripts/ calls it.

CI legs that must be read before merge, because nobody local can see them

The SQL Server leg matters most. Four tests in tests/test_store_privilege_preflight.py skip locally with no reachable listener on this box, and they are the ones that exercise the arm #1234 reports on:

:802  SKIPPED  set MEFOR_TEST_SQLSERVER=1
:831  SKIPPED  set MEFOR_TEST_SQLSERVER=1
:889  SKIPPED  set MEFOR_TEST_POSTGRES=1
:913  SKIPPED  set MEFOR_TEST_POSTGRES=1

IS_SRVROLEMEMBER, IS_ROLEMEMBER and HAS_PERMS_BY_NAME execute against a real server or nowhere. This PR adds a dedicated step per backend so they run at all; whether they pass is only visible on the runner. Read sqlserver and postgres before merging.

Also read the ide leg, which is not in ci-gate's needs, so a red there would merge without blocking.

The ci-red label on this PR is not a code failure

failure-signal.yml applied it, and the only failed run on this branch is review gate at 4cde805c, which fails by design because no reviewed label is present. I did not apply that label and must not: the gate records that a step happened, and labelling my own PR would satisfy the machine while defeating the point.

So ci-red here reports the absence of a review, not a defect. Nothing reads that label back, and a push does not strip it, so it will sit there looking like a problem until a reviewer acts. Flagging it because a label is the first thing a reader trusts.

Open questions for the Reviewer

  1. The dropped seam comment. I took main's digest scheme whole and dropped the branch's prose about store_privilege.status having four values where only observed with an empty excess is clean. I judged it redundant against the docstring in api/models.py. If you want it preserved somewhere, say where; a v-numbered seam comment would contradict #1220.
  2. The widened api/(__init__|app) arm. It makes the server-DB legs run on any change to messagefoundry/api/__init__.py. The gate test demands it and the test's own docstring calls import-reachability a deliberately wide proxy, so extra leg-runs are the safe direction. Still a real cost on a hot file, and worth a second opinion.

Marked draft on account of those two.

🤖 Generated with Claude Code

wshallwshall and others added 4 commits September 3, 2026 15:17
…nt main

Carried from w3-store-privilege-preflight (11b0a8a), a branch that never had a
PR and sits 530 commits behind main. BACKLOG #1234 names a defect in this code,
but no Builder could act on it because the subject did not exist on main. This
makes it exist.

The preflight reads the store principal's effective privileges at startup and
warns when they exceed what the engine needs. Observation and refusal are
separate: [store].require_least_privilege gates refusal and stays False, the
default the branch shipped.

The message is rewritten on carry. The original read RESCUE COMMIT and DO NOT
MERGE AS-IS and recorded a --no-verify commit, so carrying it onto main would
import a false instruction. The content is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…I legs

Carried from w3-store-privilege-preflight (d12b28d). Three things: the
postgres_excess fix that names the WRAPPER role an attribute sits on, a
dedicated live-DB pytest step per backend in ci.yml, and the seam bump.

Four conflicts against 530 commits of drift, all resolved toward main:

ci.yml path gate. Main's regex is far broader than the branch's. Kept main's
and added the branch's one real addition, store_privilege, to the tests
alternation. The store/ prefix already pulls the server-DB legs for
privilege.py; the test-file arm matters when only the test changes.

The seam trio (_ui_seam.py, webconsole __init__, golden snapshot). The branch
hand-bumped ENGINE_UI_SEAM to 19. Main retired the hand-chosen number for a
discovered digest under BACKLOG #1220, and its own seam comment names this
branch as half of the reason. Took main's scheme and regenerated: the digest
moves 93ba1f10b9dccfc8 to b93f38d097f97a45, the golden gains store_privilege
on SecurityPosture plus a StorePrivilegeView row, and the console pin is set
by hand as the tool requires. The branch's v19 seam comment is dropped; its
content already lives on the model in api/models.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fires

Carried unchanged from w3-store-privilege-preflight (ffe1afe). A site reading
store_principal_over_granted had nowhere to learn why a role it believes is
correct was named, and the two cases where that happens are the ones a careful
operator is most likely to hit.

On SQL Server a user-defined database role is reported even when it wraps
exactly the three prescribed fixed roles, because the probe reads membership
rather than a role's contents. On PostgreSQL a role attribute is reported when
it sits on any role the principal may assume, not only on its own row.

Without the link the honest operator conclusion is that the probe is broken,
which is how a posture control loses its readers. DEPLOY-SERVER-DB.md section
1.3 already carries the explanation, so this is the pointer to it and not a
restatement.

The original commit message carried a verification record taken against a
2026-08-11 baseline, 530 commits behind. It is dropped rather than repeated
here, because re-asserting a stale run as if it covered this tree would be
false. What ran on this carry is recorded in the PR body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… (PR #764)

The 2026-08-23 amendment named the clearing condition in its own words: that
branch is rebased onto main with a PR opened. PR #764 does that, so the item
moves from unstartable to startable.

It does NOT close. The defect it names is unfixed and PR #764 is a carry
rather than a fix, deliberately, because the two-arm test the item specifies
is its own reviewable change.

Three corrections to the record while I was in there:

The 328-behind figure was right when written and is now 530. The branch kept
drifting for the eleven days the row sat in the pool, which is the cost that
amendment was warning about.

The absence measurement is re-run with a stronger positive control. The
2026-08-23 reading established only that the instrument could find the word
store; require_managed_identity across four .py files is the control that
could actually have disagreed.

The defect's HAS_PERMS_BY_NAME limb is named for the first time. The item's
original scope covered the role columns only, so a fix reading just that scope
would leave a direct GRANT CONTROL SERVER mis-read exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the ci-red A required check went red. Attribute it before retrying. label Sep 3, 2026
wshallwshall and others added 2 commits September 3, 2026 15:33
The carried entry claimed a seam bump 18 to 19. That was true on the 2026-08-11
branch and is false here: main retired the hand-chosen integer for a discovered
digest under BACKLOG #1220, so this carry moves 93ba1f10b9dccfc8 to
b93f38d097f97a45 and adds StorePrivilegeView to the discovered surface.

A changelog line naming a version number that does not exist is the kind of
false record a reader has no way to catch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tests/test_serverdb_ci_coverage.py went red on this branch and it was right to.
The carried test file is a server-DB suite and imports messagefoundry.api, and
main's regex covered messagefoundry/api/app but not the package __init__. So
editing that file would not have pulled the legs that prove it.

Widened messagefoundry/api/app to messagefoundry/api/(__init__|app). The gate
binds the ASSERTED-ON file rather than the asserting one, which is why a new
suite can open a hole in it without touching the source that hole is about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wshallwshall wshallwshall added reviewed A reviewer has read this. Removed automatically when new commits arrive. and removed reviewed A reviewer has read this. Removed automatically when new commits arrive. labels Sep 3, 2026
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Label disclosure, required by the Manager playbook

I did NOT apply the reviewed label on this pull request. It was applied by another session.

I am recording that because I commissioned this diff, and the playbook this seat works to requires the commissioning party to say so when it labels its own workers' work. The honest version here is the inverse: this label is not mine, and I cannot vouch for what read stands behind it.

Every session on this machine pushes as one GitHub identity, so the actor field on the label event does not identify which session applied it. That is measured, not assumed.

The owner has disabled the reviewer role and its tag requirement, so the label now satisfies a gate rather than asserting a review. Nothing here should be read as my attestation of this diff's scope.

Four conflicts, and the seam trio could not be resolved by picking a side.

docs/CONFIGURATION.md: each side held something the other lacked. Main had
extended the `require_managed_identity` row by 414 characters with a scope
clarification citing BACKLOG #1182 (the check covers the STORE hop only). This
branch predates that and adds its own `require_least_privilege` row. Taking
either side whole would have dropped the other, so the result keeps main's
updated row and appends this branch's new one.

The ENGINE_UI_SEAM trio is a generated digest, and NEITHER side's value was
correct for the merged tree:

  this branch  b93f38d097f97a45
  main         266cbfd342b22819
  merged       2adb8d6a113e29c8

The merged surface is main's plus this branch's additions, so picking a side
would have shipped a digest describing neither while the gate went green. The
file says so itself: never hand-edit it to silence a gate. Regenerated with
`scripts/webconsole_seam_snapshot.py --write`, then the console side set by hand
in this same commit as the generator instructs. All three files agree and no
stale digest remains anywhere in the tree.

The regenerated surface now carries `StorePrivilegeView` and the
`store_privilege` field on `SecurityPosture`, which is this branch's own work
showing up in the contract -- the digest moving is the correct outcome here, not
a merge artifact.

Verified: 98 passed across the four seam-touching test files. Positive control,
because a green here otherwise proves nothing: tampering the digest to a
nonsense value fails 4 tests including
`test_the_stored_seam_equals_the_derived_digest`, and restoring it passes 8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Lander hold: this PR may land the subject of a standing owner ruling, and it never cites that ruling. Not merging until someone rules on it.

I resolved this PR's merge conflict earlier today and said it needed only marking ready. That was incomplete advice and I am withdrawing it.

What I found

docs/BACKLOG.md on main carries an owner ruling dated 2026-09-03 against item #1008: "KEEP THE PREFLIGHT DEFERRED ... THE GATE STANDS and the banner does not flip." Its stated grounds are that the runbook fix removed the cause, the preflight detects a symptom whose cause is gone, and at zero deployments demand for the detector is weak.

This PR carries the store-privilege preflight onto main. Measured on this branch:

Check Result
Adds messagefoundry/store/privilege.py yes, 412 lines
Adds tests/test_store_privilege_preflight.py yes
Callers outside privilege.py itself api/app.py (3), store/store.py, store/sqlserver.py, store/postgres.py, config/settings.py
Mentions of #1234 in title and body 7
Mentions of #1008 in title and body 0

So it is wired in, not carried dead. It is gated by [store].require_least_privilege, which defaults to False at config/settings.py:569, so nothing runs unless an operator opts in.

Why this is a question and not an accusation

This PR's own framing is reasonable. It is a clearing act, not a fix: it exists so BACKLOG #1234 stops being unstartable, because that defect lives in code absent from main and no Builder could branch to it. That is a real problem and this is a sensible answer to it.

Whether shipping a default-off detector counts as "building the preflight" is a judgement, not a measurement. A reasonable person could say the ruling defers enabling it and this changes nothing operational. A reasonable person could also say the ruling's grounds were about demand for the detector at all. I am not the one to pick.

One factual correction to the ruling itself, which is separate

The ruling's recorded side effect says #1234's defect "lives only on w3-store-privilege-preflight". That is false as written. Measured across three refs:

w3-store-privilege-preflight          privilege.py present, 412 lines
feat/store-privilege-preflight-carry  privilege.py present, 412 lines   <- this branch
main                                  privilege.py ABSENT

This branch is the second path, and it had an open PR 74 minutes before the ruling commit was written. So the "nowhere to go" premise for #1234 was already stale when recorded. That is a defect in the ruling's side-effect sentence, not in the ruling.

What unblocks this

Someone with the authority to read the 2026-09-03 ruling has to say whether it reaches this PR. If it does not, I merge on the same terms as anything else. If it does, this PR needs re-scoping to carry only what #1234 requires.

The merge conflict is resolved and every check was green before the freeze, so nothing else is holding it.

@wshallwshall
wshallwshall marked this pull request as ready for review September 7, 2026 18:11
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Owner ruling: this PR is IN SCOPE. The hold is lifted and I am landing it.

I placed a hold on this pull request earlier today because it wires the store-privilege preflight into api/app.py and all three store backends, which is the subject of the owner ruling of 2026-09-03 recorded in BACKLOG #1008 ("KEEP THE PREFLIGHT DEFERRED"). This pull request cites #1234 seven times and #1008 zero times, so nothing in it addressed the question.

The owner has now ruled that the deferral does not reach this change. That resolves the open question, and it was theirs to resolve: whether shipping a detector that defaults to False counts as building what the ruling defers is a judgement, not a measurement, and I said so rather than deciding it myself.

What I did to make it landable

Taken out of draft, which was the only remaining gate on it beyond the ruling.

Branch updated onto current main. Its one failing check was gitleaks (secret scan), completed 2026-09-07T14:12:57Z — twenty-five minutes before PR 977 merged at 14:37:53Z. That failure is from the unscoped era: the scan then walked every fetched ref, so this branch was red on fixtures belonging to other people's branches. Its head did not carry a1f3232a1, so the update is what picks the fix up rather than a rerun.

Everything else was already green: 40 passing, 8 skipped.

What still stands from the hold

The ruling's recorded side effect remains false, and that is not this pull request's fault. BACKLOG #1008 states that #1234's defect "lives only on w3-store-privilege-preflight". Measured:

w3-store-privilege-preflight          store/privilege.py   412 lines
feat/store-privilege-preflight-carry  store/privilege.py   412 lines   <- this branch
origin/main                           store/privilege.py   ABSENT

This branch is the second path, and it had an open pull request 74 minutes before the ruling commit was written. So the "nowhere to go" premise was already stale when recorded. That correction is captured on the annotated tag owner-ruling-20260903-preflight, along with the ruling text verbatim and what the anchor cannot establish.

The conflict resolution in this PR, for whoever reviews the merge

Four files conflicted. Three were the ENGINE_UI_SEAM digest, where neither side's value was correct for the merged tree:

digest
this branch b93f38d097f97a45
main 266cbfd342b22819
merged 2adb8d6a113e29c8

Picking either side would have shipped a digest describing neither tree while the gate went green. The file says so itself: never hand-edit it to silence a gate. It was regenerated with scripts/webconsole_seam_snapshot.py --write, with the console side set by hand in the same commit as the generator instructs.

docs/CONFIGURATION.md was the fused shape: main had extended the require_managed_identity row by 414 characters with a scope clarification citing BACKLOG #1182, and this branch adds its own require_least_privilege row. Taking either side whole would have dropped the other. The result keeps both.

@wshallwshall
wshallwshall added this pull request to the merge queue Sep 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 7, 2026
wshallwshall and others added 3 commits September 7, 2026 17:43
…ontract

The three seam artefacts conflicted because both sides legitimately moved the contract and the
digest is DERIVED from it, so neither side's value is right for the merge. main carried
8121b1068d584081 (the console MFA-denial audit reaching a new AuthService member); this branch
carried 2adb8d6a113e29c8 (its own additions to api/app.py and api/models.py).

Taking either would have produced a digest that describes neither tree. Resolved by regenerating
with the sanctioned writer against the MERGED code:

    python scripts/webconsole_seam_snapshot.py --write

which yields e249212f1ea3c305 -- a third value, and the correct one, because the merged contract is
the union of both changes. The console side is not written by that script and its own output says to
set it in the same commit, so SUPPORTED_ENGINE_SEAMS is set to the same value by hand.

Verified: engine constant, console constant and the golden snapshot all read e249212f1ea3c305, and
tests/test_webconsole_seam_snapshot.py passes 8 tests. The gate stays blocking; nothing is silenced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lander conflict resolution only. This commit builds nothing and claims nothing: the subject
deliberately cites no backlog number, because the claim gate reads a `BACKLOG #N` there as a
statement that this session is building that item. The items below are named as context for
what the two sides were doing, not as work taken.

Nine conflicts, and eight of them are one thing: `security_loosenings()` GREW A PARAMETER ON
EACH SIDE, so every call site now passes the wrong number of arguments.

The signature itself merged correctly and takes NINE:

    sec, store, auth, alerts, secret_rotation,
    cleartext_hops, expiry_relaxed_hops, unverified_db_hops, store_privilege

Main added `secret_rotation` (from the #1004 work); this branch added `store_privilege` (the
#1008 observation). Every conflicted call carries one and lacks the other, so neither side is
a defensible resolution -- each is a TypeError against the merged signature. All eight sites
now pass both.

The ninth, in `settings.py`, is the same collision in prose: that docstring enumerates what
the function reports and each side added its own entry. The merged sentence names all three
-- `[secret_rotation].enforce_store_key_expiry`, the three per-connection deviations, and the
store principal's OBSERVED privilege posture.

THREE MISTAKES OF MINE THAT THE CHECKS CAUGHT, recorded because the resolution looked right
each time:

  * my first resolver refused every region on a detector that missed
    `settings.secret_rotation` -- the preceding character is a dot, which my pattern did not
    admit;
  * the pattern was corrupted in transit: `\b` reached the file as a literal backspace byte
    (0x08), so it could never match. That is the documented Bash heredoc unescaping defect;
  * with the detector fixed, the paren counting was wrong. `security_loosenings(..., (), (), ())`
    has balanced parens, so my "does the call close here" test said no and the new argument
    landed on its own line, producing five files that would not parse. `ast.parse` named each
    one; `mypy` named the first. Every touched file parses now.

Checks run: `pytest tests/test_security_config.py tests/test_security_posture_defaults.py
tests/test_alert_smtp_tls.py tests/test_client_network_allowlist.py
tests/test_memory_encryption_readout.py` -- 150 passed, 1 skipped; `mypy messagefoundry` --
no issues in 270 source files; `ruff check` and `ruff format` clean. Verified the format pass
introduced no churn outside this change: every file it touched is byte-identical to main
except `api/app.py`, whose 42 added lines are this branch's own preflight wiring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…preflight-carry

# Conflicts:
#	messagefoundry/api/_ui_seam.py
#	messagefoundry_webconsole/__init__.py
#	tests/golden/webconsole_seam.snapshot
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Lander diagnosis: this needs you, not a rerun

Verdict: pr_own (confidence high). One agent read the run logs and reached this; a second agent, briefed to refute it, re-ran the checks independently and did not.

Failing jobs: test (ubuntu-latest, py3.14), test (windows-2022, py3.14), test (windows-2025, py3.14), CI gate, postgres store, sql server (store + connector) 2022, sql server (store + connector) 2025, repo harness tests (ubuntu-latest)

Failing tests:

  • tests/test_store_privilege_preflight.py::test_registry_names_an_over_granted_principal
  • tests/test_store_privilege_preflight.py::test_registry_names_an_unobservable_probe_separately
  • tests/test_store_privilege_preflight.py::test_registry_is_silent_on_a_clean_observation
  • tests/test_store_privilege_preflight.py::test_registry_is_silent_on_sqlite
  • tests/test_store_privilege_preflight.py::test_registry_is_silent_when_no_probe_result_reached_it
  • tests/test_store_privilege_preflight.py::test_the_refusal_switch_is_a_hardening_and_is_not_itself_a_loosening
  • tests/test_store_key_calendar_expiry.py::test_the_opt_out_is_a_NAMED_security_loosening
  • tests/test_store_key_calendar_expiry.py::test_the_shipped_default_is_not_reported_as_a_loosening
  • tests/test_no_store_phi_coverage.py::test_every_colliding_response_field_is_bound

What to do

The author must fix the PR. Three edits on the branch: (1) update the two pre-existing call sites in tests/test_store_key_calendar_expiry.py (lines 232 and 254) to pass the new 9th argument; (2) fix the PR's own call sites in tests/test_store_privilege_preflight.py (lines 530 and 586, helper _names and the test_the_refusal_switch... body) — they pass 8 arguments and omit secret_rotation, so add SecretRotationSettings() in 5th position, or use keyword arguments; (3) classify ('StorePrivilegeView', 'detail') in _RESPONSE_FIELD_COLUMN in tests/test_no_store_phi_coverage.py — the store column it projects, or None with the reason. Alternatively give store_privilege a None default in messagefoundry/config/settings.py, which fixes (1) but NOT (2) or (3). A rerun cannot help; merging origin/main cannot help either, though it would separately clear the non-required repo harness tests (windows-2025) install-gate leg via d6ae7e6.

How this was attributed to the branch rather than to main

merge-base = f01b991; origin/main = 6f9f7b7; PR head = 9aec954. Probe 1, git show <ref>:messagefoundry/config/settings.py | grep -c "store_privilege: StorePrivilegePosture | None,": origin/main 0, merge-base 0, pr764 1. Probe 2, required-positional-param count in the security_loosenings signature: origin/main 8, pr764 9. Probe 3, git grep -c StorePrivilegeView <ref> -- messagefoundry/**: origin/main absent, merge-base absent, pr764 present (app.py 3, models.py 4). Probe 4, git diff --stat <merge-base> pr764 touches only messagefoundry/config/settings.py (+95) and tests/test_store_privilege_preflight.py (+992), so tests/test_store_key_calendar_expiry.py keeps its 8-arg call against the PR's 9-arg signature. Absent on main AND absent at the merge base AND present on the PR, for both the new required parameter and the new response model: the PR introduced them.

Evidence

RUN FRESHNESS. gh api .../actions/runs/34241709231 --jq '.status + "/" + (.conclusion // "-")' returned completed/failure, head_sha 9aec954, event pull_request, created 2026-09-08T14:58:16Z. gh pr view 764 --json headRefOid returned the same SHA, so the run is current. An unbounded actions/runs?head_sha=9aec9546...&event=pull_request&per_page=100 returned total_count=11 with exactly ONE CI row (34241709231), so there is no newer CI run.

THE THREE KNOWN NON-PR CAUSES ARE ABSENT FROM THE REQUIRED LEGS. Over the full 11,914-line --log-failed dump: grep -c "PWSH LAUNCH TIMED OUT" = 0; grep -c "could not reach PyPI" = 0; grep -c "Treating this as a FAILURE" = 0.

THE THREE REQUIRED test LEGS FAIL ON AN IDENTICAL 9-TEST SET, all one root cause, verbatim from the log:
FAILED tests/test_store_privilege_preflight.py::test_registry_names_an_over_granted_principal - TypeError: security_loosenings() missing 1 required positional argument: 'store_privilege'
FAILED tests/test_store_key_calendar_expiry.py::test_the_opt_out_is_a_NAMED_security_loosening - TypeError: security_loosenings() missing 1 required positional argument: 'store_privilege'
FAILED tests/test_no_store_phi_coverage.py::test_every_colliding_response_field_is_bound - AssertionError: response field(s) whose name matches a PL-1/2/3 column in docs/PHI.md are not classified: [('StorePrivilegeView', 'detail')].

ATTRIBUTION (merge-base = f01b991, main tip = 6f9f7b7).
git show <ref>:messagefoundry/config/settings.py | grep -c "store_privilege: StorePrivilegePosture | None," -> origin/main 0, merge-base 0, pr764 1.
git grep -c StorePrivilegeView <ref> -- messagefoundry/** -> origin/main (none), merge-base (none), pr764 app.py:3 models.py:4.
Absent on main AND absent at the merge base AND present on the PR: the PR introduced both.

THE MECHANISM. security_loosenings() has 8 required positional params on origin/main and at the merge base; the PR makes it 9 by appending store_privilege: StorePrivilegePosture | None with NO default. Two sets of call sites still pass 8:

  1. tests/test_store_key_calendar_expiry.py (lines 232, 254) passes sec/store/auth/alerts/secret_rotation/(),(),() = 8. git diff --stat <merge-base> pr764 shows the PR touches ONLY messagefoundry/config/settings.py and tests/test_store_privilege_preflight.py, so this pre-existing caller was never updated for the new arity.
  2. The PR's OWN new tests (test_store_privilege_preflight.py:530, :586) pass sec/store/auth/alerts/(),(),(),store_privilege = 8, OMITTING secret_rotation. git show pr764:tests/test_store_privilege_preflight.py | grep -c SecretRotationSettings = 0 — the file never imports or passes one. Python binds the 8th arg to unverified_db_hops, leaving store_privilege unbound, which is exactly the error string.
    The PHI failure is the same attribution: `git show pr764:tests/test_no_store_phi_coverage.py | grep -c StoreP

(truncated)


Posted by the Lander while draining the merge queue. If any of this reads wrong, say so on the PR and I will re-check rather than defend it.

@wshallwshall

Copy link
Copy Markdown
Collaborator Author

Lander: diagnosed. Nine failures, two causes, and neither is main moving underneath you.

1. Eight of nine: a required ninth parameter with one caller file missed

TypeError: security_loosenings() missing 1 required positional argument: 'store_privilege'

messagefoundry/config/settings.py on main takes eight parameters. This branch adds a ninth, required, with no default:

store_privilege: StorePrivilegePosture | None,

So every existing caller breaks. You updated six test files. tests/test_store_key_calendar_expiry.py is not among them, and it calls security_loosenings four times.

It is not a race with main. Measured against the merge base f01b991d9:

  • the file was already present at the base, with the same four call sites
  • git log <base>..origin/main -- tests/test_store_key_calendar_expiry.py is empty -- untouched since you branched

So a rebase will not clear this, and there is no one else to wait for.

Two ways out, and the choice is a real one rather than cosmetic:

  1. Update the four call sites. Keeps the parameter required, which forces every future caller to state a store-privilege posture explicitly. For a security-posture readout that is usually the point.
  2. Give it = None. One line, no callers touched -- but then a caller that forgets it silently reports no store-privilege loosening, which is the quiet-default shape this function exists to make loud.

I would take (1) for that reason, but it is your call and I have not made it.

2. The ninth: a PHI classification, and PR 948 is blocked on the identical guard

response field(s) whose name matches a PL-1/2/3 column in docs/PHI.md are not classified:
  [('StorePrivilegeView', 'detail')]

tests/test_no_store_phi_coverage.py is a deny-by-default hinge: any new response field whose NAME collides with a classified store column reds until somebody binds it in _RESPONSE_FIELD_COLUMN -- either to the column it projects, or to None with the reason it projects none.

Worth knowing: PR #948 is blocked on the same guard, for ('ElevatedResponse', 'detail'). Two independent branches added a response model with a detail field and both tripped it. That is the guard working, not a false positive, but if a third is coming it may be worth a shared note in _RESPONSE_FIELD_COLUMN about how detail fields should be classified.

I am not writing either binding. Asserting a PHI posture from a model's docstring is the exact premise-error that module's own header records having happened before.

Not blocking

You are 33 behind main and merge clean -- I checked. All nine failures are the two causes above.

wshallwshall and others added 2 commits September 9, 2026 08:54
…ssify the posture detail

Four blockers. Three are hand-fixed here; the fourth is cleared by merging main.

security_loosenings takes nine positional parameters and secret_rotation is the
fifth. Two test files called it with eight and no secret_rotation, so
store_privilege bound to the unverified_db_hops slot and the call raised before
it ran. tests/test_store_privilege_preflight.py now passes SecretRotationSettings()
at slot five in both of its call sites. tests/test_store_key_calendar_expiry.py,
written against the eight-parameter signature, gets the ninth argument None --
"no preflight ran", which this branch's own
test_registry_is_silent_when_no_probe_result_reached_it pins as contributing
nothing, so neither calendar-expiry assertion changes meaning.

NOT by giving store_privilege a default. That was offered on the PR thread and it
is wrong: with a default, secret_rotation binds to the () that follows it, and
settings.py then raises AttributeError reading .enforce_store_key_expiry off a
tuple. The shortcut moves the error rather than removing it.

The deny-by-default PHI guard needed ('StorePrivilegeView', 'detail') classified.
Traced rather than assumed: app.py copies the value from the in-memory
StorePrivilegePosture the serve lifespan stashes, and all seven
StorePrivilegeReport construction sites build detail from a fixed literal or from
live process state -- an exception class name, a backend name, a driver message
through redact_log_line capped at 300 characters. It projects no store column, so
it is bound None, which that register defines as a claim about PROVENANCE and not
a ruling on sensitivity.

Merging origin/main clears the install-gate leg on its own:
tests/test_install_gate_allowlist_merge.py now resolves to main's fixed blob
912143c (#992), and its 32 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@wshallwshall
wshallwshall added this pull request to the merge queue Sep 9, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

CI failed while this pull request was in the merge queue, so the queue ejected it.

Its own head can still be green: the queue revalidates the merge, and the path gates that skip on a pull request run there. Read the run before retrying.

https://github.com/MEFORORG/MessageFoundry/actions/runs/34364172295

@wshallwshall
wshallwshall added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 6148f41 Sep 9, 2026
65 of 67 checks passed
@wshallwshall
wshallwshall deleted the feat/store-privilege-preflight-carry branch September 9, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-red A required check went red. Attribute it before retrying. reviewed A reviewer has read this. Removed automatically when new commits arrive.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant