Skip to content

fix(native): deliver runtime host metadata to authored projects - #324

Merged
roodboi merged 6 commits into
nextfrom
wf/e1-authored-metadata-b14
Oct 14, 2026
Merged

roodboi merged 6 commits into
nextfrom
wf/e1-authored-metadata-b14

Conversation

@roodboi

@roodboi roodboi commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

HACK-1263 (E1). Authored .hack/hack.project.json projects now receive the same runtime host metadata as Compose-file and native image projects: HACK_BRANCH, HACK_COMPOSE_PROJECT, HACK_DEV_HOST/HACK_DEV_URL, HACK_ALIAS_HOST/HACK_ALIAS_URL, HACK_SERVICE_NAME/HACK_SERVICE_URL/HACK_SERVICE_URLS and HACK_RUNTIME_METADATA. This covers both authored paths:

  • the native authored path (tryNativeAuthoredCommand): up and restart deliver the values; exec sees them in the running container, and one-off run uses the admitted environment;
  • the authored Compose renderer (renderNativeCompose).

Design

Compiler binding kind (packages/config-compiler). env_metadata accepts optional runtime: {WORKLOAD: {KEY: value}}.

  • Accepted input: only the 10 reserved keys, only for declared workloads, string values of at most 64 KiB with no NUL. Anything else refuses with a fixed request diagnostic that does not echo values.
  • Output: a new public binding {kind: "runtime", value}. It is never managed and never private-delivered.
  • Precedence, lowest first: generated metadata, then managed project values, then authored directives. So explicit authored env always wins: literal, default, env_ref, endpoint and unset all override it.
  • Host process entries never receive runtime bindings.
  • The authored plan, semantic hash and resolution hash do not change. The protocol advertises runtime_env_plan_version: 1. An older compiler refuses the unknown field, so a mismatch fails closed.
  • generated/native-config.ts is regenerated.

runtime-core. compile_inputs adds Runtime bindings to the workload's public environment, never to managed_environment. The values are part of the environment policy hash.

Shared builder. src/lib/runtime-host-metadata.ts gains buildEffectiveRuntimeHostMetadataEnvironments, which takes hosts that already have the branch applied. buildRuntimeHostMetadataEnvironments now delegates to it after applying the branch, so the Compose-override and native image outputs are unchanged.

src/lib/native-authored-runtime-metadata.ts feeds the shared builder from the compiler's routing resolution:

  • the dev host comes from the project origin, falling back to the compiler's generated <name>.hack.local;
  • the alias host comes from routes.oauth_alias;
  • each service lists its route origin before its aliases.

Wiring.

  • acquireNativeExecutionInputs takes an optional runtimeMetadata selection and plans with it. The same metadata goes into the native source envelope.
  • Native authored path:
    • serveNativeAuthoredProject passes {branch, httpsPort}.
    • tryNativeAuthoredCommand reads the native HTTPS selection with the same parser native image projects use (parseNativeHttpsSelection). A partial selection refuses before any input or runtime work, for up and restart only.
    • restartNativeAuthoredProject forwards httpsPort. A new test caught that it dropped the port.
  • Compose path:
    • prepareCommand computes the instance's Compose project before acquiring inputs, using expectedNativeComposeProject: one lstat, no state created.
    • It refuses when the opened store reports a different project.
    • It passes the same selection to the file-material owner. That keeps the existing byte-equality check between the two plans intact.
  • The renderer renders runtime bindings. It refuses one outside the reserved key set, or one for a key that an authored directive or a supplied managed value owns.
  • The TS plan parser rejects runtime bindings in host reports.

Docs and guidance.

  • docs/cli.md and docs/guides/native-candidate.md drop the "authored projects do not yet" qualifier and describe the authored behaviour.
  • The compiler README/reference and the renderer reference now document the new binding.
  • The canonical guidance in instruction-source.ts is updated. The plugin bundle is regenerated. The checked-in surfaces are re-rendered through their installers with an explicit project root: .codex/skills/hack-cli, .cursor/rules/hack.mdc, examples/basic/AGENTS.md and CLAUDE.md.
  • The content revision is now 8f9bff2d631a. New hack-cli-skill and cursor-rules fingerprints are appended.

Validation

  • Parity (tests/native-authored-runtime-metadata.test.ts, real compiler): an authored project planned for branch feature with httpsPort: 8443 yields exactly the same keys and values per service as applyNativeRuntimeMetadata for the equivalent native image project (app.hack.local, branch feature, port 8443).
  • Authored Compose render (real compiler, routed project with an OAuth alias):
    • the render gains the expected keys and URLs;
    • removing exactly the generated keys reproduces the metadata-free render byte for byte;
    • the authored HACK_DEV_URL literal is preserved;
    • negative controls: a non-reserved key, a binding that shadows an authored key, and a binding that shadows a managed value each refuse.
  • Compiler fixtures (packages/config-compiler/tests/environment.rs):
    • precedence against managed values and against literal/default/unset/env_ref directives;
    • jobs receive metadata; host entries do not; the plan and hashes are unchanged;
    • closed shape, key, workload, NUL and size checks, with no values echoed.
  • runtime-core: a runtime binding goes to the public environment while the managed value wins for its key, and the policy hash changes.
  • Native authored start: the source envelope carries runtime for every declared workload, for port 8443 with branch, no port with branch, and port 8443 without branch.
  • Command dispatch: up and restart carry the HTTPS port, and a partial HTTPS selection refuses before the owner runs.
  • Authored Compose command path (tests/native-compose-runtime-metadata-command.test.ts, added in review rework). It runs hack up through the source CLI and prepareCommand, with stand-in compiler and Docker. The stand-in compiler echoes the requested runtime selection as runtime bindings, as the real compiler does.
    • Default and --branch feature instances: the Compose document handed to docker compose up contains exactly the expected reserved keys beside the managed TOKEN.
    • HACK_COMPOSE_PROJECT equals the document name, the -p argument and an independent recomputation of the store's hack-nc-... identity.
    • The compiler received that same selection on every plan request.
    • Negative control: the checkout is replaced by a copy (same path, new inode) between metadata selection and store open. This hits the new store.identity.composeProject !== composeProject refusal. The command exits 1 with the fixed, value-free E_CONFIG_INVALID message and runs no compose up.
  • TS wire guards (tests/native-env-plan-transport.test.ts, added in review rework):
    • Runtime metadata refuses before the compiler spawns, with E_CONFIG_METADATA and no echoed value, for each of these: a non-reserved key, a lowercase key, an undeclared workload, a NUL value, a non-string value, an array workload and an array root.
    • Positive control: reserved keys reach the compiler request unchanged.
    • parseNativeEnvironmentPlan rejects a host report that carries a runtime binding. Positive controls: it accepts the same report with a literal binding, and a reserved runtime binding on a workload.
  • Mutation proofs (each made the named test fail; all reverted):
    • Compose prepareCommand passes no runtime metadata (undefined && ...) → both Compose delivery cases (rework);
    • Compose store identity check removed → swapped-checkout refusal test, which then exits 0 (rework);
    • Compose selection drops the actual Compose project → both Compose delivery cases (rework);
    • parseHostBinding accepts runtime → host-report guard test (rework);
    • runtime metadata wire accepts non-reserved keys, or undeclared workloads → wire guard test (rework);
    • managed values no longer win over runtime → compiler precedence test;
    • closed key check dropped → compiler closed-shape test;
    • runtime passed to host entries → compiler host test;
    • runtime-core drops runtime bindings → runtime-core test;
    • httpsPort not forwarded → parity test;
    • renderer ownership guard removed → renderer refusal test;
    • start acquire without runtimeMetadata → all three start tests;
    • restart not forwarding httpsPort (the real bug) → dispatch test.
  • Focused suites, each run per file:
    • authored start, restart, exec, oneoff, run, live-stop and host-processes;
    • compose file, vm-file, routed-run, routing, saved, warm-up, down, generation, inputs, host-hooks, retained-storage and storage-witness;
    • env/endpoint/network/process/routing/acquisition project suites;
    • native-up-runtime-metadata-selection, runtime-host-metadata, native-runtime-metadata, native-env-plan-transport and native-config-compiler.
    • Results: every file passes, including storage-activation (8/8). An earlier 420s per-file cap timed it out while another session was running tests on the same machine; it passes without that cap.
    • runtime-core --features native-config-plan --lib: 1202 passed.
    • Review rework, at head 20679e4 (test-only change): native-compose-runtime-metadata-command 3/3, native-env-plan-transport 45/45, native-authored-runtime-metadata 5/5, native-config-compiler 34/34. Every suite that shares the changed stand-in helper also passes: down 14, phase-trace 12, after 8, warm-up 12, storage-activation 8, retained-storage 5, storage-witness 3 and storage-fence-cost 4. integration-ownership 9/9 and cli-spawn-env-isolation 7/7 pass. packages/cli typecheck reports 0 errors, and check reports 0 errors with 34 warnings. Biome is clean on the changed tests. No source, CLI surface, guidance or Rust change was made in the rework.
    • The rework tests use temp fixtures, a private HOME through privateCliEnv, and stand-in compiler and Docker binaries. They touch no real Docker, keychain, sudo or client config.
  • Guards: tests/integration-ownership.test.ts, tests/cli-spawn-env-isolation.test.ts, agent-instruction-source, plugin generation, skill-references, hack-init-skill, shared-agent-skill, focused-skills and onboarding all pass.
  • Static checks:
    • bun run --cwd packages/cli typecheck: 0 errors.
    • bun run --cwd packages/cli check: 0 errors, 34 warnings, the same count as before; one new complexity warning was refactored away.
    • Changed tests were linted.
    • bun run docs:cli-reference produces no diff.
  • Rust 1.97.1:
    • bun run check:local and bun run test:local pass.
    • clippy and tests with --features native-config-plan pass.
    • The config-compiler fmt, clippy (-D warnings) and tests pass.

Remaining gaps (host-bound)

  • No live native run. Nothing has yet confirmed on a live native runtime or real Docker that authored containers see the env, or that exec/run inherit it. The unit tests inject the compiler, runtime and engine. The Compose command test proves that the rendered Compose document carries the keys. It does not prove what a running container's environment contains.
  • Existing graphs and generations. They gain the variables on their next up or restart. An authored Compose generation saved before this change re-renders, so it is not reused as warm.
  • Authored native graphs are unrouted. The compiler refuses routes there, so they have no alias or service URLs. A selected non-443 HTTPS port appears only in HACK_DEV_URL, matching unrouted native image graphs.
  • Branch source on the Compose path. The authored Compose path reports the routing resolution's branch when one exists, which may be a git auto-branch. Otherwise it reports the --branch instance.
  • Linear not updated (out of scope for this run).

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

roodboi and others added 3 commits October 12, 2026 22:27
…mpt and add the env secrets threat model

The `hack global dns activate|deactivate` authorize closures move into
createNativeDnsAuthorizer (src/lib/native-domain-dns-authorization.ts) with
injectable interactivity, confirmation and administrator prompt. A failed
sudo -v now throws NativeDnsAdministratorAuthorizationError (closed
outcome "authorization-failed" and failure class; message unchanged).

Tests wire the authorizer into the real activation/deactivation flows and
host effect boundary and prove: sudo -v runs before every privileged and
file effect, a failed prompt is authorization-failed (not denied) and
changes nothing, and a decline or a non-interactive run never reaches sudo.

Adds docs/security/env-secrets-threat-model.md (HACK-1127), linked from
docs/env.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efaults

tests/global-dns-authorizer-wiring.test.ts checks, on the TypeScript
syntax tree of src/commands/global.ts, that handleGlobalDnsActivate and
handleGlobalDnsDeactivate each build exactly one authorizer with
createNativeDnsAuthorizer({ action: <own action>, domain }), pass it to
createNativeDnsHostDependencies, and run their flow with those
dependencies. The only other authorizer allowed is the decline-only
`async () => false` on the deactivate verify-only path.

tests/native-domain-dns-authorization.test.ts now also covers the
production defaults: the default interactivity check requires a stdin
TTY before confirming, and the default confirmation declines an
explicitly non-interactive run before sudo. The administrator prompt
stays a fake, so no test reaches real sudo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Authored .hack/hack.project.json projects now receive the same HACK_*
runtime host metadata as Compose-file and native image projects, on the
authored native path (up/restart; exec and run inherit) and the authored
Compose renderer.

The config compiler accepts optional env_metadata.runtime values for
declared workloads (closed reserved key set, 64 KiB per value, no NUL)
and reports them as public `runtime` bindings below managed values and
every authored directive. Host entries never receive them. The values
come from the shared runtime-host-metadata builder, fed with the
compiler's effective routing hosts. runtime-core adds runtime bindings
to public workload environment, never to private delivery.

HACK-1263

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@blacksmith-sh

This comment has been minimized.

roodboi and others added 2 commits October 13, 2026 00:08
…re guards

Add a prepareCommand-level regression test: authored Compose `up` through the
source CLI renders the reserved runtime metadata keys, and HACK_COMPOSE_PROJECT
equals the generation store's hack-nc-... project for the default and a
--branch instance. A checkout replaced between metadata selection and store
open refuses before any Compose effect.

Cover the TS wire guards: runtime metadata with non-reserved keys, undeclared
workloads or non-text values refuses before the compiler spawns, and host
reports carrying runtime bindings are rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tadata-b14

Conflicted files: none. The two change sets touch disjoint files, so
git merged them automatically.

Regenerated surfaces after the merge (no resulting diff):
- bun run generate:agent-plugins
- bun run docs:cli-reference
- checked-in renders (.codex/skills/hack-cli, .codex/skills/hack-init,
  .claude/skills/hack-init, .cursor/rules/hack.mdc) compared byte-equal
  with their render functions.

integration-revision.ts and KNOWN_RENDER_FINGERPRINTS keep the values
from wf/e1-authored-metadata-b14; agent-instruction-source and
integration-ownership tests pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi changed the base branch from next to wf/e4-dns-auth-tests-threat-b14 October 13, 2026 04:42
Base automatically changed from wf/e4-dns-auth-tests-threat-b14 to next October 14, 2026 02:56
Re-parents onto next after the previous stacked PR squashed in; next's tree equals 62a9d12, which this branch already contains, so the branch tree is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi marked this pull request as ready for review October 14, 2026 02:56
@roodboi
roodboi merged commit ba5dab4 into next Oct 14, 2026
16 checks passed
@roodboi
roodboi deleted the wf/e1-authored-metadata-b14 branch October 14, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant