Skip to content

chore: merge nerdsane/temper main (#513 to #521) - #8

Merged
rita-aga merged 36 commits into
mainfrom
claude/sync-nerdsane-main
Oct 6, 2026
Merged

rita-aga merged 36 commits into
mainfrom
claude/sync-nerdsane-main

Conversation

@rita-aga

@rita-aga rita-aga commented Oct 6, 2026 •

Copy link
Copy Markdown

Merges nerdsane/temper main (1be4b7b) into arni-labs/temper main (37b3437). The two split after nerdsane nerdsane#509 (029f53e); this brings in everything nerdsane merged since:

The arni-labs fixes (#1 to #7) are kept as they are. Merge it with a merge commit (not squash) so the next sync from nerdsane starts from this point.

Overlap

The merge had no conflicts. Only three files changed on both sides, in separate places:

  • temper-jit/src/table/builder.rs: arni-labs added spawn copy, nerdsane added param_sources.
  • temper-server/src/registry_bootstrap.rs: arni-labs converts old-syntax stored specs on the platform-store path, nerdsane qualifies the Postgres restore queries with the configured schema.
  • Cargo.lock: cedar-policy-core added to temper-authz.

Authenticated parameter binding (nerdsane#513) only applies to actions whose spec declares a parameter source. Existing arni-labs specs declare none, so it does not change spawn copying or current apps.

Verification (local, merge commit 15ebecb)

This is the first PR on the fork to get a GitHub Actions run; these also ran locally with the CI commands:

  • cargo fmt --check: pass
  • cargo clippy --workspace --all-targets -- -D warnings: pass
  • cargo nextest run --workspace -E 'not test(dst_)': 3554 passed, 65 skipped (#[ignore])
  • DST core suites: 26 passed. DST platform boot, cedar, index, rollback and random: 29 passed
  • observe-gated tests (spec_validate_endpoint, observe::, api::repl::): pass
  • cargo test --doc --workspace: pass
  • readability ratchet and storage dispatch boundary: pass
  • GEPA end-to-end with the five test WASM modules built: 11 passed, including ignored ones
  • pre-push gate (fmt, clippy, ratchet, cargo test --workspace): pass

Existing flaky test

temper-wasm http_stream_outbound::outbound_streaming_1mib_roundtrip fails now and then: the 1 MiB echo comes back short (950272 of 1048576 bytes in one failure). This is not from the merge. It failed 3 of 12 runs on arni-labs main (37b3437) and 2 of 8 on this merge, and neither side changed temper-wasm since the fork point. It blocked the first push here through the pre-push cargo test --workspace. It may be a real early end-of-stream in outbound streaming, so it needs its own investigation.

Not covered

This does not change production. TemperPaw main pins arni-labs/temper by revision (37b3437 in its Cargo.lock), so TemperPaw and production move only when that pin is bumped and released. Before bumping it, check that the Katagami and TemperPaw apps still pass temper verify under nerdsane#515's stricter defaults, and that their WASM HTTP integrations call declared actions (nerdsane#514).

🤖 Generated with Claude Code

https://claude.ai/code/session_015CNkWvhUF4HU5howkdn5Ep

RetriggerConfidence Score: 4/5

The PR appears safe to merge, with two non-blocking improvements to package checks and test cleanup.

Fix All in Claude CodeFindings

  1. P2 Test listeners never stop ▶
  2. P2 Unusable modules pass verification ▶
Fix with agent prompt
### Issue 1
crates/temper-observe/tests/otel_export/listener.rs:39-44
`Listener::start` moves the socket into a detached thread that waits forever in `incoming()`. Each `run_scenario` creates another listener, but dropping `Listener` stops none of them. The sampler test alone leaves nine threads and listening sockets alive until the process exits. Keep a shutdown handle and join the thread when the listener is dropped.

### Issue 2
crates/temper-cli/src/verify/package.rs:37-43
The new compiled-module check only calls `is_file()`. An empty file or a text file named `probe.wasm` satisfies it, so an otherwise valid application can pass `temper verify` with an unusable module. Read and validate the referenced WASM bytes, and add a malformed-module regression test so authors catch this during verification rather than later.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary

This merge adds authenticated action parameters, declared HTTP admission actions, stricter application verification, explicit PostgreSQL schemas, OpenTelemetry export settings, and environment-seeded secrets.

  • No blocking behavioral defect was established.
  • Two non-blocking improvements remain: stop test listeners and check referenced WASM contents.
  • rita-aga explicitly identified outbound_streaming_1mib_roundtrip as a pre-existing flaky test and deferred its investigation; it is not reported here.
  • Review evidence comes from code inspection. No tests were executed during this review.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Request[Authenticated HTTP request] --> Route[Declared endpoint]
  Route --> Admission[Declared OData actions]
  Admission --> Checks[Cedar, trusted parameters, and IOA checks]
  Checks --> Result{Actions succeed?}
  Result -->|No| Reject[Return rejection]
  Result -->|Yes| Transport{Endpoint type}
  Transport --> WASM[Run WASM with action responses]
  Transport --> Native[Call host-installed transport]
  WASM --> Response[HTTP response]
  Native --> Response
Loading

Reviews (1) · Last reviewed commit: "Merge nerdsane/temper main into arni-lab..."

arun-pathiban-ddog and others added 30 commits September 30, 2026 19:10
Resolve declared identity inputs before action validation and persistence across OData, core dispatch and composite sub-writes; reject caller overrides.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Use a deterministic verification timestamp and exercise override rejection, queueing, and durable owner binding through the PostgreSQL OData path.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
…icated-owner-binding-upstream

feat(spec): bind action parameters to authenticated subjects
Extend HttpEndpoint with declared OData admission actions and request-scoped HTTP transports, preserving caller authority and IOA verification.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Preserve caller and module identity across local OData calls, and verify independent module secrets with compiled WASM regression fixtures.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Preserve passed evidence when serve reloads identical specs so cached verification cannot leave the application pending; cover changed and unverified specs with regressions.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Address the nine review findings with regression coverage and remove the two lint suppressions that broke the readability ratchet.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
feat(server): admit HTTP integrations through spec-declared actions
Move reusable application checks into the standard Temper CLI and reject incomplete cross-entity verification.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Keep partial standalone spec collections usable while deployment verification requires a complete application and complete cross-entity proof.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Remove verify-app and optional verification modes. Verify policies, compiled module references, matching CSDL and IOA entities, and complete cross-entity results by default. Keep source-collection behavior coverage in CI and assemble complete CLI test inputs.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Use a block scalar so the trailing Rust module separator is not parsed as a YAML mapping.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Validate qualified CSDL references and every loaded Cedar policy; route repository hooks through source checks with binding coverage and document validation helpers.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Preserve aliases through CSDL parsing and output, resolve qualified entity sets safely, and reject undeclared or ambiguous qualifiers with regression coverage.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
…ication

feat(cli): strengthen default application verification
Qualify runtime table access, retain schema selection through server metadata and registry restoration, and scope SQLx migrations to a transaction without changing default search-path behavior.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
Provide a temporary data directory for the standard server test.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-6 <noreply@localhost>
feat(postgres): support explicitly configured storage schemas
Start the telemetry setup in a child process pointed at a local OTLP/HTTP
listener, and record the startup output and every exported span, log record
and metric as a baseline. The baseline is recorded before any change to the
setup, so a later change to the default export shows up as a diff of that
file.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The tracing bridge leaves a log record's event time unset and the SDK fills
in only the observed time, so records were exported with an event time of
zero. A backend that dates records by their event time treats them as very
old and can drop them while still answering with success.

Add a log processor, ahead of the batch processor, that sets the event time
to the observed time when a record has none. Records that already carry an
event time are left untouched. The recorded baseline changes in the event
time of each log record and in nothing else.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…standard variables

Every server process reported the same identity: a fixed service name and a
fixed short list of resource attributes, so an operator running many servers
could not tell their telemetry apart.

OTEL_SERVICE_NAME, when set, replaces the built-in service name.
OTEL_RESOURCE_ATTRIBUTES is merged into the resource of traces, metrics and
logs. What the server computes itself keeps precedence: runtime-id always,
the environment and the version when their own variables are set, and the
service name unless OTEL_SERVICE_NAME is set. Entries that are not key=value
are ignored and reported in one startup warning that does not print them.

With neither variable set the resource is unchanged.

Co-Authored-By: Claude Code <noreply@anthropic.com>
With an endpoint configured, traces, metrics and logs were all exported, so
a backend that accepts only some signals still received the others.

OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER and OTEL_LOGS_EXPORTER set to
none now switch that signal's exporter off. Unset or otlp exports as before.
Any other value is reported in one startup warning and exported as otlp.

A signal that is switched off keeps its provider and gets no exporter, so
spans still carry trace context and the process logs as before. The startup
log line names the signals that are exported.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Sampling always followed the caller: a span whose caller marked the request
"not sampled" was never recorded, and there was no setting to change that.

OTEL_TRACES_SAMPLER now accepts always_on, always_off,
parentbased_always_on (the default, as before), parentbased_always_off,
traceidratio and parentbased_traceidratio, with OTEL_TRACES_SAMPLER_ARG as
the ratio. A sampler that is not supported is reported in one startup
warning and the default is used; a ratio that is not a number from 0 to 1
is reported and 1 is used.

The name-based filter did not read OTEL_TRACES_SAMPLER_ARG and still does
not. It stays around whichever sampler is chosen, so the names it drops and
the reduced-rate names behave the same under every sampler.

Co-Authored-By: Claude Code <noreply@anthropic.com>
With per-layer filters, asking the log bridge "is logging enabled?" without
logging anything can leave filter state on the thread, and the next span on
that thread is then dropped by every output.

The subscriber set up here filters globally, so it is not affected: the
export and the log lines after such a probe are the same as without it. The
test fails when the same layers are given per-layer filters (the first span
after the probe is lost), so it guards the setup against that change.

Adds log as a dev-dependency of temper-observe to make the probe. It was
already in the lock file as a transitive dependency.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…export settings

A value that is not supported in OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER,
OTEL_LOGS_EXPORTER, OTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG or
OTEL_RESOURCE_ATTRIBUTES is reported exactly once, on the log output and as
an exported log record, the server still starts, and the default applies.
An empty value is the same as an unset variable. The credential in the OTLP
headers reaches the listener on every signal and appears in no output.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The export test file had grown past 500 lines. Move the identity, signal
switch, sampler and bad-value tests into modules of their own and share one
baseline assertion. No test is added, removed or changed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Add OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES, the three OTEL_*_EXPORTER
variables, OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARG to the module
documentation and to the environment variable appendix of the guide, with
their defaults, the precedence of computed resource attributes, and what
happens to a value that is not supported.

Co-Authored-By: Claude Code <noreply@anthropic.com>
feat(observe): export settings from the standard OpenTelemetry variables, and an event time on log records
Add seed_platform_secrets_from_environment to the server's secrets module.
Given the environment as name and value pairs, it caches every variable named
TEMPER_SECRET_<NAME> with a non-empty value as the platform secret <name> in
lower case, so TEMPER_SECRET_BUILD_TOKEN supplies build_token to every tenant.

<NAME> is A-Z, 0-9 and _, starting with a letter, so no two variables can
supply the same secret. A variable that does not fit, a value that is not
UTF-8, a name the server already holds a platform secret for, and a variable
beyond the platform budget are skipped and logged once by variable name.
Seeded secrets are logged as a count. Values are never logged or returned.
Nothing is logged when no prefixed variable is set.

The function reads no environment and writes nothing to storage. Reading a
seeded secret is authorized as before: the tests go through the resolver a
module's get_secret call uses, with Cedar policies that permit and refuse it.

Co-Authored-By: Claude Code <noreply@anthropic.com>
temper serve seeded two fixed variables into the secrets cache at start,
ANTHROPIC_API_KEY and EXA_API_KEY, and had no way to supply any other secret
with the server's configuration: it had to be stored through the secrets API
once the server answered, and again after every restart of a server that
keeps its cache in memory only.

Seed every TEMPER_SECRET_<NAME> variable after the secrets the server sets
itself, so those keep their values and an existing deployment sees no change.
With no such variable set, start-up does and logs nothing new.

The test starts the built server as a process, with and without prefixed
variables, and checks the count, one warning per skipped variable, that the
fixed variable wins over its prefixed form, that no value appears in the
output, and that the server listens.

Co-Authored-By: Claude Code <noreply@anthropic.com>
arun-pathiban-ddog and others added 6 commits October 4, 2026 16:19
… precedence

Add the prefix to the environment variable appendix of the guide, with the
naming rule, the reach across tenants, the unchanged authorization, the
precedence against a tenant's stored secret and the secrets the server sets
itself, what start-up logs, and the budget.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The test file was 525 lines, over the 500-line rule. Move it to a tests
directory under the module: shared helpers, and one file each for
authorization, naming, precedence and reporting. No test is changed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The secrets API refuses a value over 8192 bytes. A prefixed variable was
cached whatever its size, so the limit depended on how a secret arrived.
Skip a larger value and log the variable by name, like the other skipped
variables. A value of exactly the limit is seeded.

Co-Authored-By: Claude Code <noreply@anthropic.com>
The guide said authorization is unchanged, next to an example of a
{secret:<name>} template. That holds for a module's get_secret call, which
needs a policy that permits access_secret. A template in an integration
config is resolved when the integration runs without that check, as it is for
every secret. Since a seeded secret reaches every tenant, say so, and say to
supply this way only what every tenant's specs may use. Add the size limit to
the guide, and a test that states the template behaviour for a seeded secret.

Co-Authored-By: Claude Code <noreply@anthropic.com>
feat(secrets): seed secrets from TEMPER_SECRET_ environment variables when temper serve starts
Comment on lines +39 to +44
std::thread::spawn(move || {
for stream in socket.incoming().flatten() {
let sink = Arc::clone(&sink);
std::thread::spawn(move || serve_connection(stream, &sink));
}
});

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 Test listeners never stop

Listener::start moves the socket into a detached thread that waits forever in incoming(). Each run_scenario creates another listener, but dropping Listener stops none of them. The sampler test alone leaves nine threads and listening sockets alive until the process exits. Keep a shutdown handle and join the thread when the listener is dropped.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/temper-observe/tests/otel_export/listener.rs
Line: 39-44

Comment:
**Test listeners never stop**

`Listener::start` moves the socket into a detached thread that waits forever in `incoming()`. Each `run_scenario` creates another listener, but dropping `Listener` stops none of them. The sampler test alone leaves nine threads and listening sockets alive until the process exits. Keep a shutdown handle and join the thread when the listener is dropped.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment on lines +37 to +43
anyhow::ensure!(
specs
.join("modules")
.join(format!("{module}.wasm"))
.is_file(),
"missing compiled WASM module {module}"
);

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 Unusable modules pass verification

The new compiled-module check only calls is_file(). An empty file or a text file named probe.wasm satisfies it, so an otherwise valid application can pass temper verify with an unusable module. Read and validate the referenced WASM bytes, and add a malformed-module regression test so authors catch this during verification rather than later.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/temper-cli/src/verify/package.rs
Line: 37-43

Comment:
**Unusable modules pass verification**

The new compiled-module check only calls `is_file()`. An empty file or a text file named `probe.wasm` satisfies it, so an otherwise valid application can pass `temper verify` with an unusable module. Read and validate the referenced WASM bytes, and add a malformed-module regression test so authors catch this during verification rather than later.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

@rita-aga

rita-aga commented Oct 6, 2026

Copy link
Copy Markdown
Author

CI run 37395914236 (the fork's first): every job passes except two. Both failures already exist on arni-labs main, and this merge changes neither file.

  • Integrity & DST Patterns / No unwrap() in production code: flags crates/temper-platform/src/os_apps/mod_test/policy_reinstall/{faults.rs,mod.rs}. Those test files came in with arni-labs fix(os-apps): reinstalling an app replaces its previous Cedar #7 (e02af69) and are identical on main. The scanner treats src/**/mod_test/ as production code. fix(os-apps): reinstalling an app replaces its previous Cedar #7 never had a CI run.
  • Tests / temper-spec::migration_differential (all_migrations_preserve_integrations, git_show_ignores_inherited_git_dir_from_other_repo): git show 53e2304^:... fails with "invalid object name". Commit 53e2304 exists only on nerdsane side branches (for example codex/startup-fs-idempotency), not in main history. The fork has no copies of those branches, so this fails on any arni-labs CI run. Neither side changed the test file.

Everything else passed: compile and lint, all four DST/platform suites, spec verification L0 to L3, instrumentation hygiene, and the other 3102 workspace tests. Locally, both of these pass because that clone also has the nerdsane branches.

@rita-aga
rita-aga merged commit 5a403ac into main Oct 6, 2026
10 of 12 checks passed
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.

2 participants