Skip to content

runtime: unify stat identity and add bounded file-backed MAP_PRIVATE mmap (Batch 32S) - #95

Open
thanks-cohn wants to merge 2 commits into
mainfrom
codex/execute-batch-32s-plan-on-main
Open

runtime: unify stat identity and add bounded file-backed MAP_PRIVATE mmap (Batch 32S)#95
thanks-cohn wants to merge 2 commits into
mainfrom
codex/execute-batch-32s-plan-on-main

Conversation

@thanks-cohn

Copy link
Copy Markdown
Owner

Motivation

  • Fix a review-found stat-identity mismatch so the same resolved namespace object reports one canonical (st_dev, st_ino) across fstat(open(path)) and newfstatat(path) while preserving lstat/no-follow symlink identity.
  • Stop treating a compact live trace as a repository diagnostic by reclassifying the mmap rejection marker into ordinary trace output.
  • Enable the smallest evidence-backed file-backed MAP_PRIVATE mapping necessary to advance the observed /sbin/apk --version frontier without speculatively implementing a full mmap subsystem.

Description

  • Unify stat identity: added resolveFinalObject and changed stat paths to derive inode identity from the authoritative namespace manifest_offset for followed resolution, while no-follow retains the symlink object identity; updated recipes/run-hosted-morphic-runtime/src/bounded_namespace_lookup.zig and recipes/run-hosted-morphic-runtime/src/freestanding_riscv64.zig accordingly.
  • Reclassified rejection trace: replaced ZIGREF_LINUX_MMAP_REJECT emission with the ordinary LINUX_MMAP_REJECT trace to avoid misusing the ZIGREF-* diagnostic namespace.
  • Added bounded file-backed MAP_PRIVATE planner and runtime path: new recipes/run-hosted-morphic-runtime/src/linux_rv64_file_mmap.zig implements a plan/prepare model that checks page alignment, file-range arithmetic, W+X rejection, rounding to pages, tail zeroing, and immutable private copying; integrated runtime mapping in freestanding_riscv64.zig via externalFileMmap that resolves descriptor->resource, reserves backing, copies file bytes into private backing, maps pages, and rolls back on partial failure.
  • Tests and build wiring: added focused tests (namespace identity and file-mmap tests), wired linux_rv64_file_mmap.zig into the run-hosted-morphic-runtime recipe in build.zig, and added a persisted handoff report docs/reports/AGENTIC_SNOWBALL_BATCH_32S.md plus a small COMMANDS.md note.

Testing

  • python3 tools/query-reference.py agent bootstrap succeeded and repository indexes were readable, while agent doctor reported an inherited missing virtualenv interpreter (recorded as an environment warning).
  • Unit/regression tests passed: zig test recipes/run-hosted-morphic-runtime/src/bounded_namespace_lookup.zig (5/5) and zig test recipes/run-hosted-morphic-runtime/src/linux_rv64_file_mmap.zig (2/2) passed, and zig build test-recipe-run-hosted-morphic-runtime completed successfully.
  • Formatting and repository checks passed: zig fmt --check on touched files and python3 tools/check-command-reference.py --check passed with no command-reference drift.
  • Real-artifact acquisition succeeded: python3 tools/pressure-real-rv64-alpine-minirootfs.py --artifact-only produced the canonical namespace and data used for runtime testing.
  • Real QEMU runs of the unchanged /sbin/apk --version artifact were exercised under the new runtime; they reliably reproduced the new behavior where the smaller libapk mapping crosses but the observed libcrypto mapping exceeds the bounded private backing (explicit capacity rejection), so /sbin/apk --version did not succeed and the libcrypto backing-capacity remains the next causal blocker (recorded in the persisted report).

Codex Task

Copy link
Copy Markdown
Owner Author

Review: I am holding the merge for a focused cleanup pass, not because the Batch 32S direction is wrong.

What looks good:

  • the PR repairs the PR runtime: implement Linux/RV64 fstat(80) and advance real /sbin/apk frontier #94 stat-identity mismatch by using the resolved object's canonical manifest offset for followed stat/open+fstat and preserves the final symlink object's identity for no-follow metadata;
  • the mmap rejection marker is correctly demoted from the authored ZIGREF-* namespace to ordinary LINUX_MMAP_REJECT trace output;
  • the new bounded file-backed MAP_PRIVATE path is general rather than apk/path/fd specific, copies immutable namespace bytes into private backing, respects requested R/W/X, preserves W+X=0, does not advance the descriptor offset, and rolls back mapped leaves on a mapping failure;
  • real QEMU causally advances the smaller libapk mapping and exposes the 320-page private-backing bound as the first blocker for the observed 950-page libcrypto mapping.

Before merge, please repair these items on the existing PR branch:

  1. CI is red. Both current checks on head 2e68b6ecf2a4e90c06ae345611e93b08f0965bcc fail at zig build check because canonical validation evidence is stale:
    validation evidence error: fixed-capacity-vector: stale source digest; regenerate validation evidence
    Regenerate the repository-owned canonical validation evidence with the established tooling, commit it, and rerun until the full validate workflow is green. Do not bypass or weaken the gate.

  2. Add permanent runtime-level mmap ownership/failure coverage. The new linux_rv64_file_mmap.zig tests currently exercise the planner/prepare layer, but Batch 32S also required the integrated resource/mapping path to be protected. Add the smallest focused testable coverage (extracting a helper if needed) for the relevant runtime invariants: invalid descriptor/unsupported resource rejection, capacity or occupied-range rejection before commitment, descriptor/file-position unchanged, and no mapping/resource leakage on a forced failure/rollback where the existing test surface supports it. Keep this bounded; do not broaden into fixed mappings or Batch 32T work.

  3. Re-prove the Playable Alpine regression before merge. The persisted report explicitly records that the persistent-shell acceptance was not rerun after these runtime mapping changes. Please rerun the canonical one-shell Playable Alpine sequence and record the result in docs/reports/AGENTIC_SNOWBALL_BATCH_32S.md.

Keep the causal frontier unchanged: do not enlarge the backing bound or implement fixed mappings in this cleanup. After the above is green and persisted, the next run should start from the observed 950-page libcrypto private-backing capacity blocker.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2e68b6ecf2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (offset > file_size) return error.FileRange;
const rounded = std.math.add(usize, length, page_size - 1) catch return error.AddressOverflow;
const mapped_length = rounded & ~(page_size - 1);
return .{ .file_offset = offset, .byte_length = @min(length, file_size - offset), .mapped_length = mapped_length, .permissions = permissions };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep pages wholly past EOF inaccessible

When length extends by one or more complete pages beyond file_size - offset, this truncates byte_length but retains the full mapped_length; externalFileMmap then installs every page and leaves the excess pages readable as zeros. Linux only zero-fills the remainder of the final file-backed page—access to pages wholly beyond EOF must fault—so this can silently fabricate file contents. Since this runtime has no SIGBUS/page-fault path for these mappings, reject such ranges or leave the beyond-EOF pages unmapped.

Useful? React with 👍 / 👎.

Comment on lines +26 to +27
const permissions: Permissions = .{ .read = protection & 1 != 0, .write = protection & 2 != 0, .execute = protection & 4 != 0 };
if (permissions.write and permissions.execute) return error.PermissionDenied;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle protection modes before mapping Sv39 leaves

For protection == 0 or write-only protection == 2, plan succeeds, but the downstream Sv39 leaf encoder rejects leaves with neither R nor X and leaves with W but not R. Consequently a syntactically accepted mmap consumes preparation work, rolls back, and reports ENOMEM rather than handling or explicitly rejecting the protection mode. Validate these combinations in the planner, reserve PROT_NONE without a leaf, or normalize Linux write permission to an encodable Sv39 policy.

Useful? React with 👍 / 👎.

Comment on lines +1865 to +1868
var rollback: usize = 0;
while (rollback < mapped) : (rollback += 1)
_ = batch26_builder.unmapPage(candidate + rollback * frames.PageSize, .page_4k) catch shutdown();
external_runtime_mappings.cancelLast(candidate, plan.mapped_length);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reclaim page-table allocations during mmap rollback

If mapping succeeds for initial pages and a later mapPage fails—for example when a range crosses a 2 MiB page-table boundary with the prepared table-page owner nearly exhausted—this rollback clears only leaf entries. Builder.unmapPage does not release now-empty intermediate page-table frames, so the failed mmap permanently consumes bounded table capacity and repeated failures can make later valid mappings fail. Preflight the required table pages or roll back the intermediate allocations as part of the transaction.

AGENTS.md reference: AGENTS.md:L174-L175

Useful? React with 👍 / 👎.

@thanks-cohn thanks-cohn left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Holding merge. The PR head is still 2e68b6ecf2a4e90c06ae345611e93b08f0965bcc, so the requested cleanup pass has not landed on this branch yet.

There are now four concrete merge blockers:

  1. CI is still red at zig build check because canonical validation evidence is stale (fixed-capacity-vector: stale source digest). Regenerate the canonical validation evidence and make the full validate workflow green; do not weaken the gate.

  2. The Playable Alpine one-shell regression is still explicitly unproven in the persisted Batch 32S report. Re-run and persist the full already-earned acceptance after these mmap runtime changes.

  3. Codex review found a real file-backed mmap correctness bug: plan() can accept a mapping whose requested length extends by one or more whole pages beyond EOF; the runtime then maps those wholly-past-EOF pages as readable zero pages. Linux only zero-fills the tail of the final file-backed page; later whole pages must not become fabricated readable file contents. Since Morphic has no SIGBUS/page-fault handling for this slice, reject ranges that would require wholly-past-EOF mapped pages or otherwise leave them inaccessible. Add a permanent regression.

  4. Codex review also found that PROT_NONE (protection == 0) and write-only (protection == 2) are accepted by the planner but cannot be encoded by the current Sv39 leaf policy. They fall through to mapping failure and surface as ENOMEM, which is semantically wrong. Handle these protection modes deliberately before mapping: reject unsupported modes deterministically, reserve PROT_NONE without a leaf if that is the chosen bounded semantic, or normalize Linux write permission to an explicitly tested encodable policy. Add permanent tests.

Keep this cleanup bounded. Do not enlarge the 320-page backing pool or start the 950-page libcrypto capacity batch yet. Once these four items are repaired, the runtime-level ownership/failure tests are present, Playable Alpine is re-proven, and CI is green, I expect this PR to be mergeable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant