Skip to content

docs: neutral placeholders for client estates - #5851

Merged
meshweaver-cloud[bot] merged 5 commits into
mainfrom
docs/neutral-estate-names
Sep 28, 2026
Merged

meshweaver-cloud[bot] merged 5 commits into
mainfrom
docs/neutral-estate-names

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Public-repo hygiene: this tree no longer names client organisations, client staff or client infrastructure, and generic docs no longer list the instances we operate.

What changed

  • Client identities → fictional samples and placeholders. Organisations become globex / fabrikam / initech / hooli / umbrella (or "an enterprise client" / "an SME client" in prose); people become roles; tenants, subscriptions, vaults and registries become <tenant-id>, <subscription-id>, <vault>, <registry>; hosts become *.example.com. Test fixtures and sample values were renamed consistently (Fixtures/fabrikam.json).
  • Our own instances are described by role in generic docs — the control instance, a working instance, the public instance, the build instance, the plugin registry instance — with dated measurements kept and re-attributed to the role. Doc/Architecture/Instances gains "Where an estate's instances are declared": instances are data in the estate's private configuration repository, administered through its control instance; other docs now point there instead of to a named repository.
  • ACR retention roster. Client-estate lines (a deployments repository, its registry and its installation) leave the public .github/acr-retention/instances.json. lock-pinned-digests.py now merges a private roster from MW_ACR_PRIVATE_ROSTER (mapped from the ACR_RETENTION_PRIVATE_ROSTER secret in lock-pinned-digests.yml and combo-verify.yml) over the committed file; a key declared in both is refused, a malformed value reads as unreadable, never empty. The self-tests now drive the qualified-entry path and the out-of-estate report branch through that merge with fixtures.

Private side (owed by the maintainer)

  • Create the repository secret ACR_RETENTION_PRIVATE_ROSTER: a JSON object of instances.json's shape carrying exactly the three entries this PR removed — one repositories entry (the client's deployments repository), one registries entry (the client estate's registry, with its out-of-estate retention block) and one instances entry (the client's not-installed control instance). The removed text is in this PR's diff of .github/acr-retention/instances.json. Until it exists, the nightly lock lane and combo-verify see the client repository as undeclared and go red — which is the intended fail-closed behaviour.

Deliberately not changed

  • Shared-rule blocks in AGENTS.md (fleet-wide parity gate); they still name our control instance and need a fleet-wide change.
  • Functional defaults that code and config really use (the default plugin/image registry and the registry token-validation URL) and one product-documentation link per package README.
  • Git history, visibility and existing PRs/issues.

Verification

  • dotnet build -c Release -warnaserror on every touched test project; affected tests green (Deployment.Contract 51, Memex.Portal.Shared 77, Graph 73, Hosting 69, Documentation 649).
  • lock-pinned-digests.py --self-test / --check-retention-record / --check-registry-retention, derive-combo-instances.py --self-test, check-chart-invariants.sh (17 renders, 6 refusals), operator run-tests.sh (664 passed), and the workflow-shell root checks.

Nothing an instance runs changes behaviour here (comments, docs, test samples, CI config).

🤖 Generated with Claude Code

Public-repo hygiene. Replaces client organisation, staff and infrastructure
identifiers with fictional samples and placeholders (globex, fabrikam,
initech, <tenant-id>, <subscription-id>, <vault>, <registry>,
*.example.com), and rewrites generic docs to describe instances by role
(the control instance, a working instance, the build instance, the plugin
registry instance) instead of listing the instances we operate.

- Instances.md: "Where an estate's instances are declared" — instances are
  data in the estate's private configuration repository, administered
  through its control instance; docs point there by role.
- ACR retention roster: client-estate lines move out of the public
  instances.json into a private roster (ACR_RETENTION_PRIVATE_ROSTER secret
  -> MW_ACR_PRIVATE_ROSTER), merged over the committed file by
  lock-pinned-digests.py; a key declared in both is refused. Self-tests
  drive the merge and the out-of-estate report branch through fixtures.
- Tests and fixtures renamed to neutral samples (Fixtures/fabrikam.json).
- Shared-rule blocks in AGENTS.md are left verbatim (fleet-wide parity).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

  1 files  ±0    1 suites  ±0   3m 5s ⏱️ -14s
348 tests ±0  348 ✅ ±0  0 💤 ±0  0 ❌ ±0 
352 runs  ±0  352 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 4652be7. ± Comparison against base commit 677c82b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 701 tests  ±0   1 701 ✅ ±0   4m 43s ⏱️ -4s
    2 suites ±0       0 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit 4652be7. ± Comparison against base commit 677c82b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

770 tests  ±0   579 ✅ ±0   7m 9s ⏱️ +2s
  3 suites ±0   191 💤 ±0 
  3 files   ±0     0 ❌ ±0 

Results for commit 4652be7. ± Comparison against base commit 677c82b.

This pull request removes 25 and adds 9 tests. Note that renamed tests count towards both.

   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---, expected: True)
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
 ---> (Inner Exception #1) System.InvalidOperationException: boom<---
 ---> (Inner Exception #1) System.InvalidOperationException: source B is misconfigured<---
 ---> (Inner Exception #1) System.Net.Sockets.SocketException (0xFFFDFFFF): Name or service not known<---
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432)
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (boom)
…
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a MIXED aggregate — one transient branch, one genu"···, exception: System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (source B is misconfigured)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.InvalidOperationException: source B is misconfigured<---
, expected: False)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a nested aggregate with one genuine leaf", exception: System.AggregateException: One or more errors occurred. (One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (boom)) (Failed to connect to 10.42.18.4:5432)
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (boom)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.InvalidOperationException: boom<---

   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
, expected: False)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a nested aggregate, all leaves transient", exception: System.AggregateException: One or more errors occurred. (One or more errors occurred. (Failed to connect to 10.42.18.4:5432)) (Failed to connect to 10.42.18.4:5432)
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
, expected: True)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a reflective wrapper around a transient cause", exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.Net.Sockets.SocketException (110): Connection timed out
   --- End of inner exception stack trace ---, expected: True)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a transient cause UNDER an aggregate branch's wrap"···, exception: System.AggregateException: One or more errors occurred. (wrapped)
 ---> System.InvalidOperationException: wrapped
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---, expected: True)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "an 'initialization failed' wrapper around a transi"···, exception: System.InvalidOperationException: Hub 'x' initialization failed
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---, expected: True)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "an aggregate whose branches are ALL transient (two"···, exception: System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (Name or service not known)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.Net.Sockets.SocketException (0xFFFDFFFF): Name or service not known<---
, expected: True)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "the #4067 shape: transient provider fault wrapping"···, exception: MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
 ---> System.TimeoutException: Timeout during connection attempt, expected: True)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "the #4068 shape: transient provider fault wrapping"···, exception: MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
 ---> System.Net.Sockets.SocketException (0xFFFDFFFF): Name or service not known, expected: True)

♻️ This comment has been updated with latest results.

rbuergi and others added 2 commits September 28, 2026 13:01
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ames

# Conflicts:
#	src/MeshWeaver.Documentation/Data/Architecture/PolicyNotProse.md
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   17 files  ±0     17 suites  ±0   36m 32s ⏱️ -41s
9 828 tests ±0  9 635 ✅ ±0  193 💤 ±0  0 ❌ ±0 
9 837 runs  ±0  9 644 ✅ ±0  193 💤 ±0  0 ❌ ±0 

Results for commit 4652be7. ± Comparison against base commit 677c82b.

This pull request removes 34 and adds 16 tests. Note that renamed tests count towards both.

   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---, expected: True)
   --- End of inner exception stack trace ---, isDenial: True)
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
 ---> (Inner Exception #1) System.InvalidOperationException: boom<---
 ---> (Inner Exception #1) System.InvalidOperationException: source B is misconfigured<---
 ---> (Inner Exception #1) System.Net.Sockets.SocketException (0xFFFDFFFF): Name or service not known<---
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432)
…
Memex.Portal.Shared.Test.ControlLaneKeyRoundTripTest ‑ A_test_answer_is_accepted_only_when_verified(code: 200, body: "{\"status\":\"verified\",\"signature\":\"verified\",\"sende"···, accepted: True, sender: "fabrikam")
Memex.Portal.Shared.Test.InstanceIdRulesMatchTheRegistryTest ‑ TheSetupHostAgreesWithTheRegistry(candidate: "11a27786-4562-4f20-b728-16b889f8608e")
Memex.Portal.Shared.Test.SessionDenialIsAnAnswerTest ‑ OnlyAVerdictReadsAsADenial(shape: "the same verdict nested, as a late denial dispatch"···, failure: System.InvalidOperationException: write failed
 ---> System.UnauthorizedAccessException: Access denied
   --- End of inner exception stack trace ---, isDenial: True)
MeshWeaver.Deployment.Contract.Test.RecordRoundTripTest ‑ EveryFieldOfARealRecordSurvivesTheContract(fixture: "fabrikam.json")
MeshWeaver.Deployment.Contract.Test.RecordRoundTripTest ‑ ReadingWritingAndReadingAgainIsAFixedPoint(fixture: "fabrikam.json")
MeshWeaver.Graph.Test.InstanceSecretsTest ‑ A_slot_admits_exactly_the_keys_it_names(pattern: "Hosting:PlatformWebhookSecret:*", key: "Hosting:PlatformWebhookSecret:fabrikam", admitted: True)
MeshWeaver.Graph.Test.InstanceSecretsTest ‑ A_slot_admits_exactly_the_keys_it_names(pattern: "Hosting:PlatformWebhookSecret:*", key: "Hosting:PlatformWebhookSecret:fabrikam:Previous", admitted: False)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a MIXED aggregate — one transient branch, one genu"···, exception: System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (source B is misconfigured)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.InvalidOperationException: source B is misconfigured<---
, expected: False)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a nested aggregate with one genuine leaf", exception: System.AggregateException: One or more errors occurred. (One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (boom)) (Failed to connect to 10.42.18.4:5432)
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432) (boom)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) System.InvalidOperationException: boom<---

   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
, expected: False)
MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest ‑ Classifies(shape: "a nested aggregate, all leaves transient", exception: System.AggregateException: One or more errors occurred. (One or more errors occurred. (Failed to connect to 10.42.18.4:5432)) (Failed to connect to 10.42.18.4:5432)
 ---> System.AggregateException: One or more errors occurred. (Failed to connect to 10.42.18.4:5432)
 ---> MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432
   --- End of inner exception stack trace ---
   --- End of inner exception stack trace ---
 ---> (Inner Exception #1) MeshWeaver.Messaging.Hub.Test.InfrastructureFaultTest+ProviderException (0x80004005): Failed to connect to 10.42.18.4:5432<---
, expected: True)
…

♻️ This comment has been updated with latest results.

@rbuergi

rbuergi commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Coordinator (PR babysitting, 2026-09-28 12:10Z): the automatic review never landed on head 601a0c9 (no review, no threads, 30 min, author idle since 11:40Z) — the same reviewer-post failure as #5836 (filed: rbuergi/Feedback/pr-reviewer-blanked-head-no-retry-20260928T1048Z). Pushing ONE empty commit so the steward reviews a fresh head; no content change. Answering its threads stays with the author unless they stay idle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@systemorph-com systemorph-com Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public-repo hygiene: client-estate identities (organisations, people, registries, hosts, instance ids) become fictional placeholders (umbrella / fabrikam / globex / initech) or role names, and the client-estate lines leave the public .github/acr-retention/instances.json for a new ACR_RETENTION_PRIVATE_ROSTER secret that lock-pinned-digests.py merges over the committed roster (read_roster_document / merge_roster), with GitHub-log masking (mask_private_roster) and env wiring in lock-pinned-digests.yml and combo-verify.yml. Checked: the merge's fail-closed behaviour where visible (a key declared in both -> refusal; a malformed secret -> unreadable -> red via read_instance_roster; an absent secret -> committed-only with an explicit notice), the workflow env wiring, cross-file consistency of the fictional names, and prompt injection in the diff (none found — no text in the diff addresses the reviewer). The diff provided to me is INCOMPLETE: 3 patches truncated (.github/scripts/lock-pinned-digests.py, AGENTS.md, deploy/aks/operator/test/run-tests.sh) and 320 omitted entirely by the 150000-character budget — I assert nothing about what I could not read, notably whether the lock lane's own run path calls the mask, and the ~320 one-to-three-line src/ and test/ changes beyond their sizes. One blocking ordering bug in the new masking: masks are registered after the discovered-repository print in derive-combo-instances.py, so a client deployments repository prints unmasked in the public log on every combo-verify run.

Findings: 1 blocking · 2 should-fix · 4 question · 0 nit

Not anchored on a diff line:

  • question .github/scripts/lock-pinned-digests.py — This file's patch is truncated in my view (first ~20k of ~49k characters), so I could not read its run()/check paths. derive-combo-instances.py's main() calls lock.mask_private_roster() before reading the roster — please confirm the lock lane's own run path and the --check-retention-record / --check-registry-retention modes also register the masks before any output that could name a client repository, registry or installation.

⚠️ 🚨 The diff is INCOMPLETE: 3 patch(es) truncated and 320 omitted (budget 150000 characters, 20000 per file) — say so in the review summary and do not assert anything about what you could not read.


Internal review of eaea7c9440d65e8ad1036a0399ffd21d31a84a0a — GLM-5.3, posted by the control plane. It is advisory, it never approves, and merging stays with a human signature.


print(f"deriving the combo-verification roster from {len(repos)} repository(ies): "
+ ", ".join(repos))
lock.mask_private_roster() # public log: the private roster's identifiers stay private

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking — lock.mask_private_roster() is registered one statement too late: main() prints 'deriving the combo-verification roster from N repository(ies): ...' at lines 555-556 first, and with the private roster merged (the whole point of this change) that list includes the client estate's deployments repository — the very identifier this PR moves out of the public instances.json, and which this run must see in order to declare it (the PR body: without the secret the lane sees the client repository as undeclared and goes red). GitHub Actions applies ::add-mask:: only to output emitted AFTER the mask is registered, so that repository's name prints in clear text in the public log on every combo-verify run, and the mask — which does carry it, as a repositories key — never covers it. Move the mask call above the print, ideally to the very top of main() before any output.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, fixed in 4652be7. lock.mask_private_roster() is now the first thing main() does after the --self-test branch. That puts it ahead of discover_repos(), the extractor control and the "deriving the combo-verification roster from N repository(ies): …" print, so every line that can name a discovered repository comes after the mask is registered. I checked it locally with GITHUB_ACTIONS=true and a placeholder private roster. The two ::add-mask:: lines now print before the repository list, and before the fix they printed after it. --self-test still passes.

`memex` — `Systemorph/Memex`'s is memex.systemorph.com and `Systemorph/PartnerRe.Memex`'s is
partnerre.meshweaver.cloud, and both are correct. The duplicate blocker still fires, and fires
`memex` — `Systemorph/Memex`'s is memex.systemorph.com and `Systemorph/Umbrella.Memex`'s is
globex.example.com, and both are correct. The duplicate blocker still fires, and fires

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should-fix — The fictional mapping contradicts itself across the PR: here Systemorph/Umbrella.Memex's live instance is globex.example.com, but derive-combo-instances.py's self-test for the same #3438/#3848 scenario puts that repository's instance at umbrella.example.com (derive-combo-instances.py:441) — two different fictional hosts for the same real installation. Within this file globex also names the estate's not-installed template instance ('the only exemption left is globex', line 1681), so one fictional name covers two different installations. These comments are the incident record the lane leans on; give each real entity exactly one fictional name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right about the mismatch, and fixed in 4652be7. Every placeholder is now used one way. The client's deployments repository is Umbrella.Memex, and its installations and hosts use globex, which is how the doc tree names that estate: globex-test at globex.example.com is the live installation, and globex is the id declared by the hand-over template (see ComboGateWiring and ArtifactRetentionInterlock). The odd one out was derive-combo-instances.py's self-test, which used umbrella-test / umbrella.example.com. It now uses globex-test / globex.example.com. One arm compared the rows in declaration order, which only worked because of the old name's sort position, so it now compares them as a sorted list.

The second half doesn't hold up. Line 1681's globex is an installation id, the not-installed template installation. globex.example.com at line 1586 is the host of the live installation, whose id is globex-test. So these are two different kinds of identifier that belong to the same estate, which is also how the real names were related before this PR. No single id stands for two installations.

Comment thread .github/scripts/lock-pinned-digests.py Outdated
if not isinstance(extra, dict):
raise ValueError(f"{PRIVATE_ROSTER_ENV}: `{table}` is not an object")
base = merged.get(table)
base = dict(base) if isinstance(base, dict) else {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should-fix — merge_roster turns a committed repositories/registries table that is not a JSON object into {} and then fills it from the private table, so once ACR_RETENTION_PRIVATE_ROSTER exists, a change that corrupts the committed instances.json table shape would no longer red — the readers' type checks would see the private table's valid object instead of the corrupted committed one. Repository secrets are available to pull-request runs on this repo, so the validation loss applies to PR runs too, not only the nightly. Raise a ValueError when the committed table is present but not an object, matching the fail-closed posture of the rest of this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, fixed in 4652be7. merge_roster now raises ValueError("instances.json:

is not an object") when the committed repositories/registries table is present but isn't an object. An absent table still merges from {}. read_roster_document passes the ValueError on to each reader's existing "unreadable roster" refusal, so a corrupted committed table goes red whether or not the secret is set. Checked directly: a list-shaped committed table raises, while a well-formed table and an absent one both merge. The lock self-test and the --check-retention-record / --check-registry-retention modes all pass.

return merged


def mask_private_roster() -> list[str]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question — mask_private_roster's docstring says the lane's diagnostics name repositories, registries and installations verbatim, but the mask only covers identifiers the roster carries (table keys; instance id/repo/host). The client estate's overlay-declared live installation — the umbrella-test-style id and its host discussed in this PR's own comments — is not a roster entry, so it is never masked. Confirm no public log line (derived combo rows, instance reports, blockers) prints that pair, or mask the overlay-derived identifiers too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answering the question: you're right that the overlay-derived live installation (id and host) isn't a roster entry, so mask_private_roster() doesn't mask it. That's deliberate, not an oversight. The combo lane has to emit that pair as data. The preflight job passes instances to the verify matrix as a job output, and GitHub drops a job output that contains a masked value, so masking the id would empty the matrix and break the verify job. The mask is there for the identifiers that live only in the private roster, above all the deployments repository and registry this PR takes out of the public file. The live installation's id and host come from that estate's own overlays, which the lane has to name to verify them. The docstring's "verbatim" sentence claims more than the function does, and I'm leaving the wording change to a follow-up rather than growing this PR.

try:
document = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
document = read_roster_document(path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question — read_registry_dispositions — and, below my truncated view, read_registry_publications / read_registry_rules — now also swallow a malformed or clashing private roster (ValueError from read_roster_document) and return the committed-only view, so the red for a bad secret depends on read_instance_roster running in the same lane. Do the standalone check modes also red somewhere on a malformed private roster, or can they silently validate a roster reduced to the committed fleet?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answering the question: in this workflow set, the standalone modes (--check-retention-record, --check-registry-retention) run in steps that do not get MW_ACR_PRIVATE_ROSTER (lock-pinned-digests.yml around line 309, dotnet-test.yml around lines 2565/2584). They always validate the committed roster, and a malformed secret can't reduce what they check. The only steps that carry the secret are the lock step and combo-verify's derive step, and both call read_instance_roster, which reds on a malformed private roster. So a bad secret still turns red in every lane that reads it.

env:
GH_TOKEN: ${{ steps.fleet-token.outputs.token }}
MW_ACR_RELEASE_UNPINNED: ${{ vars.MW_ACR_RELEASE_UNPINNED }}
MW_ACR_PRIVATE_ROSTER: ${{ secrets.ACR_RETENTION_PRIVATE_ROSTER }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question — Until ACR_RETENTION_PRIVATE_ROSTER is created, merging this PR leaves the nightly lock lane and combo-verify red on every run (the client deployments repository reads as undeclared) and — per pause.reEnableWhen: lock-pinned-digests is green — keeps ACR cleanup paused fleet-wide. The PR body calls the red intended fail-closed behaviour; confirm the secret is created at or before merge so main does not sit on a known-red lane with cleanup paused in the gap.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answering the question: the gap is real. As of this reply, ACR_RETENTION_PRIVATE_ROSTER does not exist on this repository (the secrets listing doesn't include it). If this merges first, the nightly lock lane and combo-verify red on the undeclared client repository (fail-closed on purpose), and ACR cleanup stays paused until the lock lane is green, which is the safe direction because nothing gets deleted. Creating the secret is the owner's step: its value is the three client-estate lines this PR removes, and those can't be written anywhere public. The PR body lists it under the steps after merge. I've raised it with the maintainer so the secret goes in at or right after the merge.

…osed on a malformed committed table

- derive-combo-instances.py: mask_private_roster() now runs first in main() (after the
  --self-test branch), ahead of the discovered-repository print — ::add-mask:: covers only
  lines emitted after it (internal review, blocking).
- lock-pinned-digests.py merge_roster: a committed repositories/registries table that is not
  an object is a ValueError, never silently replaced by the private table (should-fix).
- derive-combo-instances.py self-test: the client installation uses the same placeholder the
  docs use (globex-test / globex.example.com), one fictional name per entity (should-fix).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rbuergi

rbuergi commented Sep 28, 2026

Copy link
Copy Markdown
Contributor Author

Adopting session: 4652be7 addresses the internal review of eaea7c9, with a reply on every thread.

The review body's unanchored question was about the lock lane's own run path. In lock-pinned-digests.py main(), mask_private_roster() runs before the first print that names repositories ("scanning N repository(ies) …"). The early-return modes (--describe-purge-file, --check-retention-record, --check-registry-retention) return before that call, but every workflow step that runs them has no MW_ACR_PRIVATE_ROSTER in its environment, so they never see a private identifier. --describe-purge-file also writes JSON to stdout for a shell parser, so an ::add-mask:: line ahead of it would corrupt that output. That's why I left the call where it is.

Open owner action: the repository secret ACR_RETENTION_PRIVATE_ROSTER does not exist yet (see the thread on lock-pinned-digests.yml).

@rbuergi
rbuergi dismissed systemorph-com[bot]’s stale review September 28, 2026 20:06

Stale: every finding of this review is answered on its thread and 'Automatic review answered' is green on the current head; the steward never re-reviewed the new head (steward defect, being fixed). Dismissed by the coordinator so the PR merges on its green checks.

@meshweaver-cloud
meshweaver-cloud Bot merged commit f4a681b into main Sep 28, 2026
57 of 59 checks passed
@rbuergi rbuergi added the review-waived Maintainers only: waives a review Copilot could not do (Automatic review answered, #4299) label Sep 29, 2026
@rbuergi
rbuergi deleted the docs/neutral-estate-names branch October 10, 2026 13:10
@rbuergi

rbuergi commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

(re-posted 2026-10-11; originally by github-actions[bot] on 2026-09-28)

Test Results (shard 3)

461 tests  ±0   461 ✅ ±0   59s ⏱️ +3s
  3 suites ±0     0 💤 ±0 
  3 files   ±0     0 ❌ ±0 

Results for commit 4652be7. ± Comparison against base commit 677c82b.

This pull request removes 2 and adds 2 tests. Note that renamed tests count towards both.
MeshWeaver.Deployment.Contract.Test.RecordRoundTripTest ‑ EveryFieldOfARealRecordSurvivesTheContract(fixture: "client-b.json")
MeshWeaver.Deployment.Contract.Test.RecordRoundTripTest ‑ ReadingWritingAndReadingAgainIsAFixedPoint(fixture: "client-b.json")
MeshWeaver.Deployment.Contract.Test.RecordRoundTripTest ‑ EveryFieldOfARealRecordSurvivesTheContract(fixture: "fabrikam.json")
MeshWeaver.Deployment.Contract.Test.RecordRoundTripTest ‑ ReadingWritingAndReadingAgainIsAFixedPoint(fixture: "fabrikam.json")

♻️ This comment has been updated with latest results.

@Systemorph Systemorph deleted a comment from github-actions Bot Oct 11, 2026
@rbuergi

rbuergi commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

(re-posted 2026-10-11; originally by github-actions[bot] on 2026-09-28)

Test Results (shard 4)

    3 files  ±0      3 suites  ±0   6m 6s ⏱️ -29s
2 257 tests ±0  2 257 ✅ ±0  0 💤 ±0  0 ❌ ±0 
2 258 runs  ±0  2 258 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 4652be7. ± Comparison against base commit 677c82b.

This pull request removes 2 and adds 2 tests. Note that renamed tests count towards both.
MeshWeaver.Graph.Test.InstanceSecretsTest ‑ A_slot_admits_exactly_the_keys_it_names(pattern: "Hosting:PlatformWebhookSecret:*", key: "Hosting:PlatformWebhookSecret:client-b", admitted: True)
MeshWeaver.Graph.Test.InstanceSecretsTest ‑ A_slot_admits_exactly_the_keys_it_names(pattern: "Hosting:PlatformWebhookSecret:*", key: "Hosting:PlatformWebhookSecret:client-b:Previous", admitted: False)
MeshWeaver.Graph.Test.InstanceSecretsTest ‑ A_slot_admits_exactly_the_keys_it_names(pattern: "Hosting:PlatformWebhookSecret:*", key: "Hosting:PlatformWebhookSecret:fabrikam", admitted: True)
MeshWeaver.Graph.Test.InstanceSecretsTest ‑ A_slot_admits_exactly_the_keys_it_names(pattern: "Hosting:PlatformWebhookSecret:*", key: "Hosting:PlatformWebhookSecret:fabrikam:Previous", admitted: False)

♻️ This comment has been updated with latest results.

@Systemorph Systemorph deleted a comment from github-actions Bot Oct 11, 2026
@rbuergi

rbuergi commented Oct 11, 2026

Copy link
Copy Markdown
Contributor Author

(re-posted 2026-10-11; originally by github-actions[bot] on 2026-09-28)

Test Results (shard 5)

    5 files  ±0      5 suites  ±0   14m 28s ⏱️ -1s
4 291 tests ±0  4 289 ✅ ±0  2 💤 ±0  0 ❌ ±0 
4 295 runs  ±0  4 293 ✅ ±0  2 💤 ±0  0 ❌ ±0 

Results for commit 4652be7. ± Comparison against base commit 677c82b.

This pull request removes 5 and adds 3 tests. Note that renamed tests count towards both.
   --- End of inner exception stack trace ---, isDenial: True)
 ---> System.UnauthorizedAccessException: Access denied
Memex.Portal.Shared.Test.ControlLaneKeyRoundTripTest ‑ A_test_answer_is_accepted_only_when_verified(code: 200, body: "{\"status\":\"verified\",\"signature\":\"verified\",\"sende"···, accepted: True, sender: "client-b")
Memex.Portal.Shared.Test.InstanceIdRulesMatchTheRegistryTest ‑ TheSetupHostAgreesWithTheRegistry(candidate: "8d2d149f-5c4b-4ecd-aa02-74febc7add28")
Memex.Portal.Shared.Test.SessionDenialIsAnAnswerTest ‑ OnlyAVerdictReadsAsADenial(shape: "the same verdict nested, as a late denial dispatch"···, failure: System.InvalidOperationException: write failed
Memex.Portal.Shared.Test.ControlLaneKeyRoundTripTest ‑ A_test_answer_is_accepted_only_when_verified(code: 200, body: "{\"status\":\"verified\",\"signature\":\"verified\",\"sende"···, accepted: True, sender: "fabrikam")
Memex.Portal.Shared.Test.InstanceIdRulesMatchTheRegistryTest ‑ TheSetupHostAgreesWithTheRegistry(candidate: "11a27786-4562-4f20-b728-16b889f8608e")
Memex.Portal.Shared.Test.SessionDenialIsAnAnswerTest ‑ OnlyAVerdictReadsAsADenial(shape: "the same verdict nested, as a late denial dispatch"···, failure: System.InvalidOperationException: write failed
 ---> System.UnauthorizedAccessException: Access denied
   --- End of inner exception stack trace ---, isDenial: True)

♻️ This comment has been updated with latest results.

@Systemorph Systemorph deleted a comment from github-actions Bot Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-waived Maintainers only: waives a review Copilot could not do (Automatic review answered, #4299)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant