Skip to content

Derive every Core address from one public URL and report installation facts - #138

Merged
SaladDay merged 20 commits into
mainfrom
codex/public-url
Sep 25, 2026
Merged

SaladDay merged 20 commits into
mainfrom
codex/public-url

Conversation

@SaladDay

@SaladDay SaladDay commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Phase 1a of the new-user install work: every Core address comes from one public URL, and a new read-only endpoint reports the installation's facts. This PR pairs the backend (Core, client, installer) with the Web changes (#135) because the sandbox deployment API change breaks the old wizard.

Core

  • One address setting. AGENTS_API_PUBLIC_URL is now the only public-address setting. Everything else is derived from it:
    • the daemon WebSocket URL;
    • the self-hosted remote_url;
    • the hosted bootstrap address;
    • node configuration;
    • the sandbox deployment core_url.
  • Retired variables. AGENTS_API_DAEMON_WS_URL and AGENTS_API_CONFIG_FILE now fail startup and name what to do instead. There is no fallback.
  • core_url in POST/PUT /core/v1/sandbox/deployment. Sending it returns 400 invalid_request_error with param: core_url. Responses still carry it read-only, and it is always present.
  • E2B and loopback. An E2B selection with a loopback public URL returns 409 sandbox_configuration_error. With a saved E2B deployment, new hosted Sessions are refused the same way. Setup reads and cleanup of existing sandboxes keep working.
  • Migration 000075_public_url_binding. It drops runtime_deployment.core_url and records each node's enrolled address. Nodes on another address get no new sandboxes and must be re-added; work already placed on them finishes there. The down migration refuses when it can't restore the address.
  • GET /core/v1/installation. Core key only, no-store, and it works before any deployment exists. It returns:
    • the public URL, API base URL, local-only flag, source commit and installation ID;
    • a non-secret settings snapshot from AGENTS_API_SETTINGS_FILE, with sensitive items carrying only configured;
    • counts of what is bound to the address.
  • AGENTS_API_DATABASE_PASSWORD_FILE. Core and migrate can read the database password from this file instead of the URL.
  • Logging. PARSAR_LOG_* now takes effect in Core and Web; log.Init was never called before. With auto, logs are JSON when stderr is not a terminal.

Client (packages/agents-client)

  • Deployment inputs no longer have core_url. SandboxDeployment.core_url and SandboxNode.core_url are read-only.
  • AdminClient.retrieveInstallation() returns CoreInstallation, with a strict projection.
  • E2B writes: a 409 sandbox_configuration_error passes through as a new AgentCoreError, only when neither the message nor the param contains the submitted key. Every other E2B write failure is still rewritten.

Installer

  • It generates the new variables. valid_core_origin applies exactly Core's origin rule to --public-url.

Web (#135)

  • The sandbox wizard no longer asks for a Core address. The review step shows the public URL read-only, and the E2B loopback 409 is shown with the config path and apply command.
  • A "how to call" card under every newly issued key gives OPENAI_BASE_URL / OPENAI_API_KEY, with curl and Python examples.
  • System shows the installation facts and read-only startup settings.
  • The Nodes page names nodes that are bound to an old address.

Docs

  • Updated: the upgrade table, the sandbox deployment contract, the new installation contract, CONTRIBUTING, the operator guides and docs/web.
  • The quickstart and the public API guide use OPENAI_BASE_URL / OPENAI_API_KEY.

Review

  • Backend: independent blind review found no P0 or P1. The three P2s and the P3s were fixed in 1431360e and verified by tests and the gate, per the small-fix rule. One P3 is deferred to phase 2: enrollment should carry the node's own address.
  • Web (Read Core's address from config.json and show the installation in the console #135): two blind review rounds. The first found P1s, fixed; the second found no P0 or P1.
  • Docs/web changes were reviewed by the backend session.
  • A stray build binary was removed from history before merge.

Verification

  • Full local server gate on a1b623f4 (the exact head, which includes main 697c6a91): passed.
  • Playwright browser cases were skipped on the server (Chrome unavailable; approved skip). The frontend ran the full e2e locally: 27/27.
  • make openapi shows no drift, and migration tests cover up and down (Docker and E2B).

Compatibility

  • Manual deployments must replace AGENTS_API_DAEMON_WS_URL with AGENTS_API_PUBLIC_URL, reusing the deployment's previous core_url so nodes stay current. They must also delete AGENTS_API_CONFIG_FILE. See the upgrade table in operations.md.
  • Installer-managed installs will be converted by phase 1b's install.sh --convert, which merges next with no release in between.

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

… facts

Core now takes the installation's address from one setting,
AGENTS_API_PUBLIC_URL. The daemon WebSocket URL, the self-hosted
remote_url, hosted Runtime bootstrap, node configuration and the sandbox
deployment's core_url all derive from it. AGENTS_API_DAEMON_WS_URL and
AGENTS_API_CONFIG_FILE are retired: setting either fails startup and names
the replacement.

The deployment no longer stores a Core address. POST and PUT
/core/v1/sandbox/deployment reject a core_url member with 400
(param core_url); responses keep core_url as a read-only field, present
before configuration. E2B selections return 409
sandbox_configuration_error while the public URL is loopback. A
migration moves the saved address onto each enrolled node. Nodes record
the address they enrolled with and report it as core_url; a node bound
to another address receives no new sandboxes, while its retained
sandboxes can still resume.

GET /core/v1/installation (Core key) reports the public URL, API base
URL, source commit, installation ID, the installer's settings snapshot
(AGENTS_API_SETTINGS_FILE, validated at startup so a sensitive setting
never carries a value) and counts of what is bound to the public URL.
It is available before any sandbox deployment exists.

AGENTS_API_DATABASE_PASSWORD_FILE keeps the database password out of
the URL for the server, migrations and maintenance commands. Core and
the console now apply PARSAR_LOG_LEVEL, PARSAR_LOG_FORMAT and
PARSAR_LOG_ADD_SOURCE, which were documented but never read.

The installer writes the new names only: AGENTS_API_PUBLIC_URL (or the
loopback Core origin without --public-url), the password file and a
URL without a password. The local node setup no longer sends core_url.

The client drops core_url from deployment inputs, marks the deployment
and node core_url read-only, adds AdminClient.retrieveInstallation with
strict projection, and passes Core's public-URL rejection through for
E2B writes unless it reflects the submitted key. Docs and the Core
OpenAPI follow; application examples use OPENAI_BASE_URL and
OPENAI_API_KEY.
…rule

An E2B selection saved while the public URL was reachable stayed usable
after the URL became loopback, so new hosted Sessions would have created
sandboxes that cannot reach Core. Session placement now refuses them
with 409 sandbox_configuration_error. The saved provider still loads, so
existing E2B sandboxes remain reachable for cleanup and startup is not
refused.

The installer checks --public-url with valid_core_origin, which accepts
exactly the origins Core's ValidateSandboxCoreURL accepts, including
IPv6 literals. It now rejects underscores, trailing dots, non-ASCII
hosts and non-canonical ports, which Core would refuse at startup. Both
tests share the same cases.

The client passes Core's public-URL rejection on as a new error with
only its message and param. AGENTS_API_CONFIG_FILE now says to remove
it instead of naming a replacement.

A migration test covers the refused rollback of an E2B deployment. The
upgrade guide lists the retired address settings and tells operators to
reuse the deployment's core_url; docs no longer describe a saved or
submitted Core origin, and the deployment contract says that work
already placed on a node finishes there.
The sandbox wizard no longer asks for a Core address or sends core_url:
its review shows the deployment's read-only address (config.json's
public_url) with where it is set. When Core rejects a configuration it
cannot serve (409 sandbox_configuration_error, such as E2B without an
HTTPS public_url), the wizard keeps Core's message and adds the config
file and apply command from GET /core/v1/installation; nothing is marked
uncertain. Nodes warns when nodes are still bound to an old address.
One card, under the one-time key in the issued-key dialog and the pending
key notice on Projects and keys: OPENAI_BASE_URL and OPENAI_API_KEY from
the installation's API base URL and the new key, a curl request, a Python
example with the pinned openai==3.13.0 and a note that execution needs a
model. Without a public API address it says the API is only reachable
locally. The key stays in the flow's memory only.
System lists the public address, API base URL, installation ID and source
commit, then the config.json startup settings read-only: each value
(sensitive ones only as configured or not set), default and fixed markers
and the services a change restarts, under the copyable file and apply
command. The sandbox section links to the Nodes page where it changes.
The fixture serves GET /core/v1/installation (public, local-only and
stale-node modes), rejects core_url on deployment writes with 400, returns
a read-only core_url and rejects E2B with 409 sandbox_configuration_error
when public_url is not HTTPS. Acceptance covers the wizard without an
address field, the E2B rejection, the how-to-call card, the startup
settings and the stale-node warning.
DESIGN.md: the wizard shows Core's address read-only from config.json's
public_url and explains an E2B rejection inline; the how-to-call card; the
System page's sections; the Nodes old-address warning. PRODUCT.md: System
and the OPENAI_BASE_URL/OPENAI_API_KEY guidance beside a new key.
docs/web: the Platform row, the Agents API row, a GET /core/v1/installation
row and the deployment row's read-only core_url.
The real client reports each node's enrolled core_url and requires
api_base_url to be public_url followed by /v1, so a loopback install has
a local API base URL and local_only set. The how-to-call card then shows
the samples under a note that only the Core machine reaches them, and
System marks the API base URL the same way. Test node fixtures carry
core_url. The e2e fixture uses one UUID installation ID, derives
api_base_url and the stale-node count from the nodes' core_url, and the
add-node E2B test no longer fills the removed address field.
The card no longer points at a System page setting that ships separately: a model provider comes with each request, from the saved Agent or from the deployment default.
The Nodes warning now comes from the node snapshot the page already
reads: a node whose core_url differs from the deployment's core_url
(config.json's public_url). It updates with every refresh, removal and
enrollment, names the nodes and says to remove and add them again, as a
status rather than an alert. Refreshing the page also re-reads the
installation the wizard uses.
The Shell sample exports OPENAI_BASE_URL and OPENAI_API_KEY together, so
a shell that already exports one of them cannot send the new key to
another base URL. Each sample's copy button reports a refused clipboard
and selects the sample for a manual copy, as the key's own button does.
Without a public address the card says to set public_url, and a skeleton
replaces the loading line.
After a save attempt clears the E2B key, the review says to enter it
again and links back to the E2B step instead of leaving Save silently
disabled. A loopback public_url carries a caution that only the Core
machine reaches it. The E2B rejection test also checks that the key is
gone from the page.
A write that sends core_url gets Core's 400 (invalid_request_error,
param core_url, its message), and E2B on a loopback public_url gets
ErrSandboxPublicURLUnreachable's message. The installation's binding
counts follow the fixture's allocations and executor credentials.
A local install's public_url is a loopback HTTP address, so the help no longer calls it the one HTTPS address.
Core rejects E2B whenever public_url is loopback, whatever its scheme, so the wizard's caution asks for a public_url other machines can reach, and comments and docs name the loopback rule.
Installation and Sandboxes show facts-only placeholders and Startup settings a table placeholder, all silent; one status says the page is loading while either first read is out. TableSkeleton stays silent without a label.
Setup answers 200, a core_url member is rejected before any state
check, and hosted_sandboxes is the deployment's allocations plus
pending for every provider. The setup test waits for Add node and the
recorded write instead of the Runtime text, and the stale-node test
proves removal alone clears the warning.
Stale node names join as a list in the current language. The samples'
copy buttons read "Copy the Shell sample" / "复制示例:终端". A unit
test ties the Python sample's openai pin to contracts/agents-api/upstream.json,
and PRODUCT.md names the how-to-call card on the first-run screen.
ErrSandboxPublicURLUnreachable no longer ends with "then save again".
@SaladDay
SaladDay merged commit b37e43b into main Sep 25, 2026
4 checks passed
SaladDay added a commit that referenced this pull request Sep 25, 2026
…osted model gap (#140)

* Store deployment model providers in Core and require a provider for hosted and self-hosted Sessions

Model credentials get one visible home and the self-hosted model gap from the
fc19bd5 acceptance (D2, D3) is closed.

- Deployment default model providers are a runtime setting: one complete
  bundle per harness in PostgreSQL (migration 000075), sealed with a new
  harness-bound credential purpose, managed only with the Core key through
  GET /core/v1/harnesses and GET/PUT/DELETE
  /core/v1/harnesses/{harness}/model-provider. Reads return the safe view
  (api_key_configured, updated_at), never the key. Writes are audited as
  deployment-wide admin_audit_log entries with a null tenant/project scope.
- Provider precedence stays Session, saved Agent, deployment default. The
  default applies to openai_hosted and none Sessions and is frozen through the
  existing encrypted Session snapshot; later edits never reach existing
  Sessions. self_hosted Sessions now accept Session and Agent bundles, delivered
  over the existing daemon connection, and never use the deployment default.
- openai_hosted and self_hosted Sessions without a bundle are rejected at
  creation with 400 model_provider_required. Legacy rows without a snapshot are
  rejected at message admission and at dispatch instead of falling back to a
  built-in endpoint. deploy/install/model_provider_sessions.py counts them
  read-only before an upgrade.
- AGENTS_API_EXECUTION_OPTIONS_FILE is retired and stops startup with the
  replacement named. Historical snapshots keep their frozen native options.
  The execution-configuration read shows the safe view of new deployment
  selections.
- packages/agents-client: listHarnesses, retrieveHarnessModelProvider,
  setHarnessModelProvider, deleteHarnessModelProvider, CoreHarness,
  HarnessModelProvider; audit entries may have project_id null.
- Contracts, docs and the Core OpenAPI are updated. Tests that create hosted
  or self-hosted Sessions use shared provider fixtures instead of relaxing the
  check.

* Name only existing steps in the retired execution options startup error

The error for AGENTS_API_EXECUTION_OPTIONS_FILE now says to remove the setting
and set deployment default model providers in Web (System) or with
PUT /core/v1/harnesses/{harness}/model-provider. The import command it named
does not exist yet; the import work adds it back.

* Record caller intent wherever the deployment default applies and address review

P2-1: A same-key retry of a none Session conflicted after the deployment
default changed, because the resolved provider fed the retry hash while caller
intent was recorded only for openai_hosted. Caller intent is now recorded
whenever the deployment default may apply (openai_hosted and none), and a
provider key enters any stored hash only as a fingerprint keyed by the
credential key (new Cipher.Fingerprint), never directly. Without the key, an
intent lookup cannot match and creation reports the missing key. none Sessions
now compare caller intent on retry, like hosted ones; rows without recorded
intent keep the resolved-equivalence rule.

Review follow-ups:
- Input reserved before providers were required fails with
  model_provider_required (new environment_input_reservations.failure_code,
  migration 000075) instead of waiting for its deadline, and the Session
  reports that reason.
- The 000075 down migration also refuses while deployment model providers
  exist.
- Scope wording: the deployment default applies to openai_hosted and none,
  never to self_hosted, in the public API guide, the self_hosted 400 message,
  the client JSDoc and related docs.
- Tests: none retry after the default is rotated and removed; a self-hosted
  preparation carries the frozen provider only to the bound executor; legacy
  reservations settle as failed.
- Docs: a saved Agent's provider key reaches every self_hosted executor
  created with that Agent in the Project; legacy none Sessions run with the
  device's own environment and are not counted by the pre-upgrade check.

* Test that intent is always recorded where the deployment default applies

The vault intent unit test assumed a none Session without Vaults records no
caller intent. none and openai_hosted now always record it; the unrelated
inline identity case moves to self_hosted, where the default never applies.

* Renumber the deployment model providers migration to 000076

Main's public URL binding (#138) took 000075.

* Keep none retry equivalence and prove the keyed retry fingerprint

P2-1: Recording raw caller intent for none Sessions broke the documented
retry equivalence (omitted, null and explicit medium verbosity). none keeps
the resolved-request rule again; caller intent stays hosted-only as before.
The resolved hash now leaves out a deployment-sourced provider bundle and its
configuration marker, so setting, rotating or removing the default does not
change a none Session's retry identity. The earlier test edits for the
intent-for-none rule are reverted.

Review follow-ups:
- withoutProviderKey fails closed: an intent whose extension or provider
  cannot be read is rejected, and the provider is re-encoded from its typed
  value with the key replaced by its keyed fingerprint.
- Cipher.Fingerprint rejects a NUL in the purpose.
- New test: the same request under two credential keys gets different
  request_hash and creation_request_hash; an unreadable provider intent is
  rejected.
- The legacy test again asserts that a rejected submit queues nothing.
- The none retry test also retries with a spelled-out default verbosity.
- model-execution.md notes that key-bearing retries first sent before this
  release, or after a credential key change, conflict once.
SaladDay added a commit that referenced this pull request Sep 26, 2026
… parsar (#151)

* Give every process setting one home in config.json, applied with parsar

A new installation writes <install>/config.json, the only file an operator
edits, with every setting that applies to its mode. The installer flags only
seed it. deploy/install/config.schema.json describes it; config_model.py
validates the small keyword subset the schema uses and a test keeps the
schema inside that subset.

The installation directory now holds secrets/ (one copy of each secret),
state.json (identity and install facts) and generated/, which parsar apply
derives from config.json: compose.json and core.env without secrets, the
Core key digest file, the native unit, the settings snapshot Core serves at
GET /core/v1/installation and runtime-history.json when set. Core reads the
new AGENTS_API_PUBLIC_URL, AGENTS_API_DATABASE_PASSWORD_FILE and
AGENTS_API_SETTINGS_FILE; the retired AGENTS_API_DAEMON_WS_URL and
AGENTS_API_CONFIG_FILE are no longer generated.

The parsar zipapp in the installation directory runs status, start, stop,
apply and rotate-core-key without the bundle. apply refuses hand-edited
generated files, changed fixed fields and changed secrets, confirms a public
URL change that strands bound nodes, recreates only the services whose
inputs changed (a Compose label carries each service's input digest) and
restores the previous files when Core rejects the new ones.
rotate-core-key cuts over at once. install.sh reruns read config.json,
reject flags and repair; --status and --stop name their replacements.

install.sh --convert moves an installation made before config.json to this
layout and release. Its preflight changes nothing and stops on anything it
can't convert; secrets are renamed, not copied; an interrupted conversion
resumes. An AGENTS_API_EXECUTION_OPTIONS_FILE stays as it is until the model
settings import, which hooks into parsar apply.

scripts/config-reference.py renders the settings table in
docs/configuration.md from the schema; make check-distribution checks it.

* Describe config.json, parsar and --convert in the install and operations docs

CONTRIBUTING now names config.json as the single operator file instead of
forbidding a new configuration format. The install guide covers changing
settings after installation, repair reruns and converting an earlier
installation; the operations guide uses parsar status, start, stop and
rotate-core-key. Key paths move from admin/ to secrets/.

* Keep parsar status and start usable when config.json is invalid

status and start report why config.json can't be used and fall back to the
last applied settings, which the running services use. A missing retained
execution options file names itself instead of failing generically.

* Fix false hand-edit reports, native restarts and conversion edge cases

Review round 1 on 8158ef0.

- apply recomputes the files to write after stamping the snapshot, so
  settings.json is written whenever its digest is recorded. Rotation and
  applies that restore a file no longer report settings.json as edited.
- An apply records the digests it is about to write before writing, then
  commits state.json. A run interrupted in between is finished by the next
  apply instead of being reported as hand edits; status says so.
- Native Core restarts only when its inputs changed, including on the
  install.sh repair path where enable --now leaves an active unit alone.
- A failed first start leaves the apply unfinished, and --convert finishes a
  converted installation whose first start failed.
- Web-only conversion before its Core notes the older Core instead of
  failing; the next apply records the pairing.
- The conversion preflight refuses secret files that are links or readable by
  others, starts an old native unit by path, lowercases an old public URL
  when case is the only problem and records no core.key digest.
- A public URL change needs confirmation when the applied value can't be
  read; state.json records the applied public URL.
- Rotation restores the old key only before the new apply is recorded;
  status reports a Core that rejects secrets/core.key and a mismatched digest
  file, and config.json fields that disagree with state.json.
- The origin check refuses IPv6 literals, underscores, trailing dots,
  non-ASCII hosts and leading-zero ports.
- rotate-core-key reads state under the lock, the Compose marker escapes $,
  and unused imports are gone.

* Accept exactly the origins Core accepts, including bracketed IPv6

The public URL and web.core_url check now uses the rule of PR 1a's installer
valid_core_origin, which matches Core's ValidateSandboxCoreURL: bracketed IP
literals are accepted like Core accepts them, while underscores, a trailing
dot, non-ASCII hosts and leading-zero ports stay refused. After 1a merges,
config_model calls that one shared function.

* Word first starts and repairs accurately in the installer output

Found in the mx1 real check. A first apply says "Services to start" rather
than restart, and only a new installation says it has no execution node; a
repaired or converted installation keeps its nodes.

* Converge on what runs instead of recorded apply state

Review rounds 1 and 2 both found P1s in interruption bookkeeping, so apply
no longer decides restarts from state.json. Every Compose service carries
the digest of its inputs in the io.parsar.inputs label, and the native unit
carries it as PARSAR_INPUTS, which apply reads back from the Core process.
apply renders config.json, compares with the running services and recreates
or restarts exactly those that differ, Core first. A Core that rejects
secrets/core.key is restarted, then Web. An interrupted apply, rotation or
rollback is finished by the next apply, and status reports services that run
with other inputs than config.json renders.

state.json keeps identity and install facts and the last digests written for
each generated file, used only to detect hand edits: a file is edited only if
it matches none of them and differs from the current render; a missing file
is written again. Rollback happens only when every service ran with the
previous files; rotation never rolls back. The applied and pending records
are gone.

--convert now accepts installs made by the #138 installer (public URL,
password file, no retired variables) as well as earlier ones; retired
variables may be missing and a hand-set AGENTS_API_PUBLIC_URL becomes
public_url. The test fixtures come from both earlier generators, kept
verbatim in deploy/install/testdata/.

Also: rotation stops Web inside its error handling and a stale core.key.new
is removed; failed first starts name the right rerun command, and a local
node requested at install is enrolled by the repair; parsar refuses a
symlinked or non-private installation, secrets/ or generated/ directory; a
resumed conversion checks its bundle and may repeat --public-url; the old
Runtime history file is removed once config.json holds it; the public URL
confirmation lists only nodes on the old address; and a Web-only dry run
sends no key to a changed web.core_url.

* Say that an interrupted rotation was interrupted

Found in the mx1 recovery check: the message had an empty reason.

* Convert a local-only #138 install without a public URL, and check native hosts first

Review round 3. At #138 the sandbox deployment's core_url is derived from
AGENTS_API_PUBLIC_URL, including the loopback fallback, so conversion no
longer adopts it when core.env names the address: the #138 value is used as
is and the loopback fallback becomes public_url null, keeping Web's origin on
its own port. Both --convert branches run the native preflight before
anything changes. A fresh install writes state.json before config.json, and
a directory left with only state.json gets a clear message.

* Record rolled-back files and count bound nodes from Core's bindings

Review round 3. A rollback records the digests of the files it restores, so
repeated rejected applies no longer end in a false hand-edit report. The
public URL confirmation counts bound nodes from /core/v1/installation's
address_bindings, so a failed node list read still requires confirmation.
The running-state read skips one-off Compose containers, parsar stop checks
the directories, a failed command names its program, and a rollback that
could not start the previous files says so.

* Match Go's loopback rule, require format 1 and note native Web restarts

Review round 3. valid_core_origin treats an IPv4-mapped loopback address as
loopback, as Go does. format must be the integer 1, not true or 1.0.
ports.core restarts Web too with native Core; the schema's native_restarts
annotation carries that into the settings snapshot and the generated table.

* Tell a live installation without config.json to restore it, never to start over

Focused review. Only a directory that never applied anything (no recorded
generated files, empty generated/) counts as an interrupted fresh install,
including one stopped before state.json was written; an installation that
lost config.json is told to restore it from a backup, with
generated/settings.json for the last applied values, since deleting it would
destroy the credential key and orphan the database. An unreadable
installation.json gets conversion's own message.

* Keep a hand edit visible after a rollback restores it

Focused review. A rollback records only restored files that parsar wrote and
that were not edited by hand, so a hand edit it puts back is still reported.

* Refuse hand-set Core addresses that would move Web, and map loopback to none

Focused review. A hand-set loopback AGENTS_API_PUBLIC_URL other than Core's
own fallback is reported, and in all mode a hand-set address that differs from
installation.json's public_url stops conversion until --public-url names the
one to keep, instead of silently moving Web's origin. A pre-#138 deployment
core_url equal to Core's loopback address means no public URL. The core.env
value is not canonicalized twice, and its message points at config/core.env.
The docs say the deployment is read only for pre-#138 installs. Tests: the
deployment-read assertion now watches HTTP requests, the native core-only
ports case is back, and the hand-set loopback and resume preflight paths are
covered.
@SaladDay
SaladDay deleted the codex/public-url branch October 7, 2026 06:38
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