From 26ff3513eb21f7912c1e6acfc849995d93e8f19c Mon Sep 17 00:00:00 2001 From: Omry Yadan Date: Wed, 28 Oct 2026 18:51:40 +0800 Subject: [PATCH] E-design: close execution-map readiness gaps Specify the E1.1 candidate, protected-reservation and admitted-assignment contract, including versioned native admission provenance and exact active-map projection. Reuse the existing operation journal and owned create boundary. Record bounded Go exec and descriptor-closure mechanism evidence, its production qualification limits, revised slice estimates and the fresh design stamp. Preserve the approved public model and stop before E1.1 implementation. --- docs/.review/MULTI_IDENTITY_E_DESIGN.md | 12 +- docs/MULTI_IDENTITY_E_DESIGN.md | 240 +++++++++++++++++++----- 2 files changed, 200 insertions(+), 52 deletions(-) diff --git a/docs/.review/MULTI_IDENTITY_E_DESIGN.md b/docs/.review/MULTI_IDENTITY_E_DESIGN.md index 368bd0e7..7a3b1117 100644 --- a/docs/.review/MULTI_IDENTITY_E_DESIGN.md +++ b/docs/.review/MULTI_IDENTITY_E_DESIGN.md @@ -3,18 +3,18 @@ artifact: swe-design-review-attestation schema_version: 4 scope_key: 9b4170e67f9abd8153de58192aec49a1a7bb74a0d3299729c834de814d6b2fd5 scope: {"kind": "path", "primary_target": "docs/MULTI_IDENTITY_E_DESIGN.md", "repository": ".", "selector": "docs/MULTI_IDENTITY_E_DESIGN.md"} -review_content_identity_sha256: f7931c9717c011cc43e45570629707feaaed4f86a38ccbaead9f891544e59b8f -target_content_identity_sha256: 627fdb35fe84027958bdaeb11ea572bab0498ccd839d2b45e6a0402bb64d123d +review_content_identity_sha256: 05a3e20d506c3f81e7c152d095a76bbeaa35fd9936218db92adff3ab917f8d2c +target_content_identity_sha256: 12a205b6d1aafc8f9f3361d80244fcd43650569362786245fc9bd0d01b124ba9 baseline_content_identity_sha256: 17972b262e20f4dccbd006b043997b1dc12e2bb7f58cd0bf32f21bba95041036 -target_documents: [{"executable": false, "path": "docs/MULTI_IDENTITY_E_DESIGN.md", "repository": ".", "sha256": "25164eecbcc8131cd5d5aec1d6aa756a7019b44f6895978533a7ebc5c201122f"}] +target_documents: [{"executable": false, "path": "docs/MULTI_IDENTITY_E_DESIGN.md", "repository": ".", "sha256": "ab5e9be8075947bc37c863545efb1959fa7aca886226018729bfeff6f7032e1e"}] baseline_documents: [{"executable": false, "path": "docs/MULTI_IDENTITY_D_DESIGN.md", "repository": ".", "sha256": "078e63e84d432c2aad03120ca30e40ec4572a8970e6bf6be9d5008608a30f435"}, {"executable": false, "path": "docs/MULTI_IDENTITY_IMPLEMENTATION_PLAN.md", "repository": ".", "sha256": "3ef44a7a0be43648f5df1faad39b16101978662c58c87d6fa8e7409093a58fe9"}, {"executable": false, "path": "docs/MULTI_IDENTITY_MAIL_RECONCILIATION.md", "repository": ".", "sha256": "fe188bb8a80f7f235e99b023283749a3a9e3172e4adf70b70b495d80e50e9218"}, {"executable": false, "path": "docs/MULTI_IDENTITY_SECURITY_CONTRACT.md", "repository": ".", "sha256": "b049714f4a85d0f3fb204615897374377690564627bfcdd03e50c945fb042826"}] design_dependency_documents: [{"executable": false, "path": "docs/MULTI_IDENTITY_D_DESIGN.md", "repository": ".", "sha256": "078e63e84d432c2aad03120ca30e40ec4572a8970e6bf6be9d5008608a30f435"}, {"executable": false, "path": "docs/MULTI_IDENTITY_IMPLEMENTATION_PLAN.md", "repository": ".", "sha256": "3ef44a7a0be43648f5df1faad39b16101978662c58c87d6fa8e7409093a58fe9"}, {"executable": false, "path": "docs/MULTI_IDENTITY_MAIL_RECONCILIATION.md", "repository": ".", "sha256": "fe188bb8a80f7f235e99b023283749a3a9e3172e4adf70b70b495d80e50e9218"}, {"executable": false, "path": "docs/MULTI_IDENTITY_SECURITY_CONTRACT.md", "repository": ".", "sha256": "b049714f4a85d0f3fb204615897374377690564627bfcdd03e50c945fb042826"}] document_repository: "." document_path: "docs/MULTI_IDENTITY_E_DESIGN.md" -document_revision_provenance: "8a0f78c19f15e007db9a28344dd6754f0a9587a7" -document_sha256: 25164eecbcc8131cd5d5aec1d6aa756a7019b44f6895978533a7ebc5c201122f +document_revision_provenance: "2470352914133d542fb6ea20ccfb9600f4edef29" +document_sha256: ab5e9be8075947bc37c863545efb1959fa7aca886226018729bfeff6f7032e1e verdict: clean -attested_at: 2026-10-28T07:17:31Z +attested_at: 2026-10-28T10:48:35Z --- diff --git a/docs/MULTI_IDENTITY_E_DESIGN.md b/docs/MULTI_IDENTITY_E_DESIGN.md index 0c01ed44..d0ee9b94 100644 --- a/docs/MULTI_IDENTITY_E_DESIGN.md +++ b/docs/MULTI_IDENTITY_E_DESIGN.md @@ -97,32 +97,123 @@ unsupported entry before create. Never execute image NSS to resolve identities. ### Retained state and acceptance -Keep the existing revision, installation binding, accepted assignments, dormant -IDs, admitted generation, engine binding and pending operation journal. Introduce -an explicit native process-model discriminator in a versioned state record. -Recommended encoding: `identity-mapping-v2` with -`process_model: native-master-v1`, existing engine binding and no supervisor -binding for native admission. Preserve v1 reading and its supervisor semantics. -Old readers must reject v2 rather than reinterpret it. Do not write a fake -supervisor identity, silently clear a legacy binding or infer conversion from -new generation contents. - -New native installations and never-admitted inhibited intents can enter v2 -through an explicit checked operation under the existing lock. A legacy admitted -supervisor installation remains reserved and inhibited for the native path; -process-model migration is separate work, not an automatic upgrade in E. -Failure to support that legacy case must identify the conflicting binding and -leave its reservations unchanged. - -Configuration intent and execution acceptance are distinct checks. B's -`ObserveEffects` continues to require the active geometry to equal the pending -configuration candidate. E's acceptance step checks the complete generation -closure against that candidate and the previously accepted retained ledger. -Every active exact ID must be declared or an explicitly accepted, image-locked -runtime account. Every newly introduced exact assignment must be active; surplus -new assignments are rejected. Previously admitted assignments may remain dormant -and reserved without appearing in active kernel maps. Bounded intervals keep -their approved full authority and do not invent names for unused numbers. +Use versioned `identity-mapping-v2` with `process_model: native-master-v1`, +existing engine binding and no supervisor binding for native admission. Preserve +v1 reading and its supervisor semantics. Old readers reject v2. Do not write a +fake supervisor identity or infer conversion from new generation contents. + +Use three numeric sets, separately for UID and GID, retaining affine host bindings: + +- C: selected requested configuration candidate, including the selected profile. +- R: complete protected reservation union, including B recovery and uncertain work. +- P: assignments previously authorized by a committed native admission transaction. + +P is a subset of R with identical container-to-host associations. The existing +v1 UIDAssignments/GIDAssignments are R, not P. Recover verifies owned effects and +adds reservations without setting AdmittedGeneration. A later non-null global +AdmittedGeneration cannot prove which rows in that union were admitted. Never +copy that union into P or promote it because a container exists. + +In v2 retain UIDAssignments/GIDAssignments for R. Add AcceptedUIDs/AcceptedGIDs +for P. Existing DormantUIDs/DormantGIDs describe P minus the last admitted active +map only; never-admitted reservations are derived as R minus P and remain +protected. AdmittedGeneration and engine/process binding describe the latest +admission. P plus dormant fields reconstruct its active map, avoiding another +persisted active-map copy. Empty P requires absent admitted generation and empty +dormant sets. Nonempty P requires the native binding, admitted generation, valid +both-domain geometry and P subset R. Bounded P, once admitted, is the complete +unchanged accepted range. Partial or ambiguous v1 admission cannot migrate. + +Existing v1 admitted supervisor state remains readable/reserved and refuses the +native path. A never-admitted v1 state may enter v2 under the checked lock with +P empty and its complete R, retained objects and pending intent preserved. An +old unresolved operation must be reconciled or exactly adopted by its owning +operation, never silently overwritten to create a new admission. + +### E1.1 input and output + +Proposed internal entry: PlanIdentityExecutionV1(input) -> plan or error. +Input carries the selected immutable generation digest and validated D policy +lock, C, optional prior R and P, and runtime-required user/group names for the +explicitly selected runtime tuple. E1.2 constructs this input under the existing +operation lock from exact versioned state, image/generation and current mapping +revision; a caller-supplied numeric geometry is not historical admission proof. +The planner validates consistency but does not authenticate host state itself. +Runtime-required names must already be explicitly declared in the D policy and +verified by its immutable-image evidence. They cannot insert IDs or enlarge C. +All declared users/groups contribute to the required identity closure, not just +the currently selected role. Same numeric ID aliases do not create isolation. + +The output contains Active, ProspectiveReservations, ProspectiveAccepted, and +DormantUIDs/DormantGIDs. Prospective means a proposed post-admission value, not +an already authorized mutation or reusable release token. E1.1 performs no I/O, +state mutation, engine call, callback, host check or payload release. It returns +independent canonical values and deterministic, redacted generation diagnostics. + +For exact mappings: +1. Validate policy/image evidence, generation, both-domain geometry and profile. +2. Use existing RetainGeometry to reject changed protected bindings or host-ID + reassignment, preserving the entire R even for an ID omitted by C. +3. Require every declared/runtime-required ID to be covered by C. Project A from + those identities using C's exact host associations; do not inherit omitted + IDs from R. Root/runtime setup accounts require explicit declared evidence. +4. Require C subset (A union P), with identical host associations. A new surplus + row fails even when it was reserved by an earlier never-admitted operation. + Previously admitted inactive rows may be present in C but not in A. +5. Return Active=A, ProspectiveReservations=R union C, + ProspectiveAccepted=P union A, Dormant=(P union A) minus A. Preserve R minus + ProspectiveAccepted as never-admitted reserved authority, not dormant IDs. + +For bounded mappings retain the complete C as Active and ProspectiveAccepted; +unused numeric IDs are accepted range authority and need no fabricated names. +All declarations and explicit runtime accounts still need coverage and evidence. +Existing bounded R/P cannot change geometry. Do not substitute exact for bounded. + +Use interval validation/union already in identitymapping. Compare canonical +unions to reject unexplained exact rows; subtract canonical interval boundaries +for dormant sets. Work scales with declarations and interval boundaries, never +with the number of numeric IDs in a range. Reject zero-sized, overlapping, +overflowing, sentinel-containing, mixed-profile or contradictory inputs. + +### E1.2 integration and commit boundary + +One existing operation journal remains the owner. Keep configure semantics: +ObserveEffects(configure) requires active geometry equal to Candidate C and +retains B's existing proof rules. A distinct closed native-execute operation +stores Candidate C plus its computed Active A and generation/lock/map/provenance +binding. ObserveEffects(native-execute) requires the exact stored A and reruns +its fresh owner/revision/generation, host-authority and planner checks. Unknown +kinds, missing A and stale plan bindings reject. Native-execute exists only in +v2; configure cannot opt into projection by supplying a smaller map. + +Reuse the current C creator, immutable-image binding, parent-map compiler, +endpoint fences and owned cleanup. Extend their existing per-operation observation +boundary; do not create another engine transport, policy cache or observer service. +The create request uses A, not R or an exact C containing dormant historical rows. + +B configuration/recovery can extend R, never P. The host's successful locked +admission transaction atomically commits the exact A into P, updates generation, +process/engine binding and dormant sets, and retains the exact owned release +attempt before release. A lost response is uncertain execution, not another +release or permission to infer acceptance from engine inspection. The waiter +alone never writes P. Errors before that transaction leave P unchanged; all +reservations and uncertain object ownership remain protected. H owns eventual +checked reservation release; E never shrinks R as cleanup. + +### E1.1 acceptance examples + +| Prior provenance / candidate | Required result | +| --- | --- | +| R reserves never-admitted UID 300; current declarations need 200; C={200} | Active={200}; retain 300 in R minus P | +| Same history; C={200,300} | Reject surplus 300; reservation is not admission | +| P contains admitted 200; later B recovery reserves 400 | Preserve 400 as never-admitted; global AdmittedGeneration does not promote it | +| P contains old 300; current declarations need 200; C may include 300 | Active excludes 300; prospective P retains it; Dormant contains 300 | +| Prior binding changes host ID or reassigns protected host authority | Reject; no cleanup shrinks R | +| Bounded 0..511 with undeclared 201 | Preserve whole range; reject out-of-range 600 | + +E1.1 owns the pure planner and numeric tests. E1.2 owns versioned state, +provenance sourcing and atomic persistence. E1.3 owns runtime observations, +framed release and owned cleanup. No release is authorized by a planner result. Do not make runtime-required accounts automatic. For example, the private Podman fixture needs GID 5 for devpts. Production acceptance requires exact image evidence @@ -197,19 +288,44 @@ with nonempty P/E requires a distinct successful mechanism proof before support; otherwise reject that tuple. Do not add ambient capabilities, file capabilities, set-ID recovery or an implicit root-only portable policy to fill this gap. -The implementation must address Go's process/thread semantics explicitly. All -threads that can reach the application must have the final authority, and no -stronger thread may survive exec preparation. Do not claim that locking one OS -thread alone proves this. The E2.1 implementation spike must choose and test the -smallest reliable arrangement using existing helper/process primitives; if a -new permanent helper or trust model appears necessary, stop for realignment. - -Close every private descriptor before application exec. Preserve only explicitly -constructed stdin/stdout/stderr and approved application handles. Test leaked -control sockets, namespace/root directory descriptors, pidfds and pre-opened -files outside a later chroot. CLOEXEC marking, enumeration and exec must not -race with a stronger thread opening descriptors. A final clean fd listing alone -does not establish how earlier access was prevented. +### Go thread and descriptor transition + +Use a locked launching OS thread in the existing trusted startup process. Before +it changes per-thread credentials, construct and validate all arguments, +environment, raw exec pointers, policy inputs and application streams. Perform +thread-local raw credential/capability syscalls on that thread. Other Go threads +may still have setup authority while only trusted startup code runs. No +application code, callbacks or application-selected hooks may run in that window. +LockOSThread alone is not proof: successful Linux exec replaces the process image +and destroys the other threads. Post-exec tests must inspect every surviving +application thread and all final credential/capability fields. + +The last transition must detach the launch thread's descriptor table and close +all private descriptors atomically using `close_range(3, ~0U, +CLOSE_RANGE_UNSHARE)`. This design preserves only constructed fd 0/1/2; it does +not introduce additional inherited application handles. The old shared Go table +can still gain descriptors, but they cannot enter the launch thread's detached +table. CLOEXEC-only marking or enumerating and closing a shared table is +insufficient. Test private files, directory descriptors, sockets, pidfds and +high-numbered descriptors, including a concurrent opener on an old table. + +After closure, enter a bounded raw exec-or-exit tail with prevalidated pointers. +Use no Go allocation, scheduler transition, logging, callbacks or ordinary error +cleanup there. Do not call ordinary `syscall.Exec` after closure: its argument +conversion and runtime preparation occur before its raw exec syscall. Close or +exec failure must terminate the entire trusted startup process with a raw +exit-group syscall, never return to a Go caller with a partially closed table. +Unavailable/denied close_range or UNSHARE support refuses; no shared-table fallback. + +The disposable Go 1.26.8 linux/amd64 binary demonstrates this mechanism from a +non-leader thread with stronger peer threads still present before exec. Its +final-tail disassembly contains raw syscall calls only, without allocation, +stack-growth or panic calls. This is a planning result, not qualification of +the production binary. E2.1/E2.2 must bind source, compiler, architecture and +emitted tail, and repeat the thread/fd adversaries with actual setup and effective +policy. Include runtime signal/preemption behavior and all failure paths in that +qualification. A compiler/runtime upgrade invalidates a binary-specific result. +Do not transplant the fixture's amd64 syscall numbers to another architecture. ### Effective policy and immutable code @@ -338,8 +454,39 @@ fd adversaries, production lifecycle, F networking/storage, or release repeats. These successful mechanisms narrow implementation uncertainty; they do not satisfy those remaining gates or qualify WSL as a supported production host. +Additional readiness evidence lives at +`/home/omry/.local/state/awd/reploy-E-readiness-20261028`. +`proof-summary.json` binds sources, static binaries and the final receipt +`probes/attempt-20261028T102248Z/result.json`, SHA256 +`d926570014867a8323bd05c634120565940bc6a8cb5214ed94fe88551fb249e1`. +The final-tail disassembly SHA256 is +`f8d3b6641e3ccff807c2742e74e7d9f481c30d96d815200f6cadc0d14e7a950f`. + +All 34 bounded cases passed on Go 1.26.8 linux/amd64, WSL kernel 6.18.54.1: +24 positive cases (three fresh runs per exact/bounded profile for empty root, +empty UID/GID 200, Postfix and Dovecot ceilings) and ten negative controls. +The controller installed and read back complete kernel maps before release. +Each positive began with non-CLOEXEC private file, directory, socket, pidfd and +high-numbered canaries, and a concurrent old-table descriptor creator. After +non-leader exec, every observed thread had the exact final UID/GID, groups, +P/E/B/I/A and NNP; no canary survived and application stdin remained intact. +Negative controls independently detected omitted closure, omitted bounding-set +drop, shared-table closure, invalid close flags and failed exec. All children +were reaped. The earlier successful matrix is retained; its binary had unreachable +slice-bounds panic branches. Prevalidating raw pointers removed those branches, +and the complete matrix was repeated against the final binary. + +This raw-user-namespace experiment ran synthetic trusted code, not the production +Go helper, native mail programs, production seccomp or framed release transport. +It does not qualify nonzero UID with nonempty capabilities, Go 1.25.x used in CI, +other architectures, actual immutable-code closure, occupied-container exec or +F production admission. Those remain explicit implementation/activation gates. + Primary technical context: [Linux capabilities and exec](https://man7.org/linux/man-pages/man7/capabilities.7.html), +[Linux exec thread semantics](https://man7.org/linux/man-pages/man2/execve.2.html), +[Linux close_range](https://man7.org/linux/man-pages/man2/close_range.2.html), +[Go LockOSThread](https://pkg.go.dev/runtime#LockOSThread), [OCI 1.2.1 process configuration](https://github.com/opencontainers/runtime-spec/blob/v1.2.1/config.md), [OCI Linux configuration](https://github.com/opencontainers/runtime-spec/blob/v1.2.1/config-linux.md), [crun 1.14.1 exec implementation](https://github.com/containers/crun/blob/1.14.1/src/libcrun/container.c). @@ -359,24 +506,24 @@ limit, and reassess before crossing the target. Keep acceptance-coherent changes | Slice | Production LOC | Test LOC | Scope and acceptance | Prerequisites | | --- | ---: | ---: | --- | --- | -| E1.1 | 250–400 | 450–700 | Pure execution identity/map closure; exact active vs retained/dormant rules, runtime-account evidence, boundary/overflow rejection. Own mapping compiler/acceptance planner, no engine effects. | D4, C1 | -| E1.2 | 300–450 | 550–800 | Versioned native admission state and locked transition; legacy supervisor rejection, uncertain operations/reservation preservation. Own deploy mapping state/operations. | E1.1 | +| E1.1 | 250–400 | 500–800 | Pure execution identity/map closure; explicit C/R/P input/output and active vs never-admitted/dormant rules, runtime-account evidence, boundary/overflow rejection. Own mapping compiler/acceptance planner, no engine effects. | D4, C1 | +| E1.2 | 350–500 | 650–950 | Versioned native admission state and locked transition; explicit accepted-subset provenance, native-execute journal projection, legacy supervisor rejection and uncertain reservations. Own deploy mapping state/operations. | E1.1 | | E1.3 | 350–500 | 600–900 | Owned create/wait, complete kernel maps and process/peer fences, single release protocol and cleanup failures. Own mapped Podman create and trusted waiter plumbing. | E1.2, C2; release still requires F proof | -| E2.1 | 300–450 | 550–850 | Empty/nonempty final capability paths and all-thread exec transition, exact credentials and securebits. Own probe startup transition; do a bounded Go mechanism spike first. | E1.1; startup mechanism receipt | +| E2.1 | 300–450 | 550–850 | Empty/nonempty final capability paths and all-thread exec transition, exact credentials and securebits. Own production startup transition; qualify the exact compiler/binary against retained Go mechanism evidence. | E1.1; startup mechanism receipt | | E2.2 | 200–350 | 450–700 | Descriptor closure and protected executable/dependency plan; canary-fd and substitution tests. Own startup fd handling and runtime-policy integration. | E2.1; E owns proof seam, F later supplies production observations | | E2.3 | 300–450 | 650–950 | Derived effective kernel policy for accepted tuples; capability-conditional rules, ABI clone/fork/threads and mount/handle negatives. Own mapped seccomp policy; ordinary path unchanged. | E2.1 | | E3.1 | 300–450 | 550–850 | Native-master lifetime, owned lease/queue cleanup and fresh host replacement; disable engine-autonomous restart. Own existing lifecycle integration. | E1.3, E2.2, E2.3 | | E3.2 | 150–300 | 350–600 | Typed occupied-container exec boundary; deterministic pre-effect refusal for unproved tuples, safe-entry proof harness. No enablement implementation in this estimate. | E3.1 | | E4.1 | 200–350 | 650–950 | Shared final-authority checks across every controlled entry; privilege-regain and failure-path adversarial payloads. Own entry integration; keep public guards. | E3.2 | | E4.2 | 100–250 | 600–950 | Cross-entry/profile qualification harness and evidence receipts, repeated-instance tests, integration-gate diagnostics and delivery closeout. Own conformance tests and docs. | E4.1; actual F/G readiness reported separately | -| **Total** | **2450–3950** | **5400–8250** | Ten proposed implementation PRs, conditional on proof results. | Design approval/stamp/PR cycle before implementation | +| **Total** | **2500–4000** | **5550–8500** | Ten proposed implementation PRs, conditional on proof results. | Design approval/stamp/PR cycle before implementation | The old E estimate was 1400–2550 production and 2800–4550 test LOC. It did not itemize the state/process-model change, setup versus application policy, thread/fd proofs, or entry/lifecycle parity now visible in the source. The new estimate counts that existing E scope; it adds no service or public feature. Compared with D's measured 1606 production and 3331 test changed lines, E is approximately -1.5–2.5 times the production work and 1.6–2.5 times the test work. Do not interpret +1.6–2.5 times the production work and 1.7–2.6 times the test work. Do not interpret these ratios as elapsed-time or token forecasts. E can finish its internal behavior and missing-proof refusal before F exists. @@ -407,5 +554,6 @@ actual unresolved public syntax or material authority/default/scope changes. For each implementation PR, derive every current `ci.yml` job for the exact head, require all to succeed, run the supported review/PR cycle, use Sapling and add a Changie fragment for user-facing changes. Do not merge or release automatically. -Stop after E closeout and propose F; this planning task stops before E production -edits, commits or publication. +Stop E delivery after closeout and propose F. This readiness amendment completes +its design review, stamp, publication and PR cycle, records readiness in the paused +campaign, and stops before E1.1 implementation. It does not resume that campaign.