You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Plan 034 (specs/034-opendox-standalone-operation/tasks.md, read at openxFactory main91e4685f, after T007 batch H landed as openxFactory#1206 → f99a2097), phase-3 slice P3-I, install mode and the bundle:
T070 (#1144's 13.4, 13.5 and 13.6): OPENDOX_INSTALL_MODE, and the --local flag.
Falsifier: F13.1's refusals, plus a test of the disagreeing pair.
the phase-3 draft-ahead widening, 5901112350 ("Install chain + lens (Recommended)").
Claimed on openxFactory#656 in 5901575394. DRAFT, authored ahead: it does not go READY before T063 lands and the holder says so.
What it does
The selector (13.4): OPENDOX_INSTALL_MODE, values local and hosted, defaulting to hosted. It is a SETTINGS entry read in runtime/config.py, placed immediately beside OPENDOX_OIDC_ISSUER. A blank value reads as unset, which means hosted: "It is UNSET, not local, that must be safe."
The flag (R1Q15 (b)): generate-and-open --local selects local exactly as OPENDOX_INSTALL_MODE=local does. With neither, the install is hosted (13.5). The flag belongs to the verb and follows it (10.1).
A flag and a setting that disagree are refused, naming both. Example: --local beside OPENDOX_INSTALL_MODE=hosted. No explicit selection is silently overridden. This is plan 034's fail-closed reading (Principle VII), not #1144's text. No answer rules the pair, batch H does not write it into #1144, and evidence/analyze-round-2.md (U2-1, V2-6) records it for Brett.
Local needs no broker.RuntimeSettings.oidc_issuer and oidc_audience are empty, and oidc_jwks_url is None. jwks_url() and discovery_url() return "" rather than a path glued onto nothing.
Local binds loopback only, with no opt-in (13.4). A non-loopback --host on generate-and-open, or OPENDOX_BIND_HOST for the runtime's own listener, is refused, naming the loopback rule.
The set is serve.py's own LOOPBACK_HOSTS (127.0.0.1, ::1, localhost). 13.4 asks for "the same judgement at the mode's own boundary".
config cannot import serve, so it spells the set, and a test holds the two equal. 127.0.0.2 is therefore refused, because the document server does not treat it as loopback either.
Hosted, or unset, with no issuer refuses, naming OPENDOX_OIDC_ISSUER (13.5).
generate-and-open asks for the issuer first (require_the_hosted_issuer), and then loads the whole runtime configuration. The serving process is the one whose settings are the install's (13.4a; R1Q16 (i)).
So a hosted run with nothing configured names the issuer and --local, not the OPENDOX_DATABASE_URL that load_settings happens to ask for first (plan 034's requirement-13 scenario 2).
load_settings keeps its own order for every verb that relies on it.
Hosted is otherwise unchanged (13.6): same broker, same pinned issuer, same order of refusals, and a hosted bind may still be 0.0.0.0.
The shape is resolved first.generate-and-open resolves it before it scans, mints or binds anything, so each refusal exits 1 at once. That covers F13.1's test "$rc" -ne 124.
Holder readings (coordinator, 2026-09-30, on openxFactory#656; Brett may overrule)
opendox-runtime runtime serve refuses under local (refusal: local-mode-has-no-broker). Every /api/v1 route verifies a broker-signed token, and a local install is served by generate-and-open --local. In release 1 its document surface reads nothing from the store (R1Q16 (ii)).
runtime status under local reports broker_keys: "not configured (local mode)" and broker_discovery: null. It never builds a verifier, and its exit code is the database's verdict alone, so F13.1's set -e survives.
A broker setting beside local is refused, naming each one: OPENDOX_OIDC_ISSUER, OPENDOX_OIDC_AUDIENCE and OPENDOX_OIDC_JWKS_URL. The values are never repeated. T072 adds the two DSNs to the list.
An unrecognised value (Local, single-user …) is refused, naming local and hosted, and matching is case-sensitive.
Deliberately NOT here
T070 is the identity half; T072 is the datastore half. Under local, load_settings still takes the DSNs from the environment, and generate-and-open --local loads no database setting. T072 supplies both DSNs from the bundled server (13.1) and refuses operator DSNs beside local.
T073's install block is not here. serve.py and pyproject.toml are untouched.
Outside src/ and tests/
deploy/ — one line, and the task requires it.deploy/compose/.env.example gains OPENDOX_INSTALL_MODE=hosted with its comment. config.py's header makes .env.example the place every setting is named, and tests_runtime/test_deploy_shape.py::test_every_runtime_setting_is_documented_in_env_example is parametrized over SETTINGS, so a new setting without the line is red. Neither the compose file nor the Kubernetes base changes: the default is already hosted.
docs/: untouched. 10.3's README line belongs to T076, in the openDox root.
One existing test changes, tests/test_doxbench_entrypoint.py's fixture. It drives cmd_generate_and_open with no settings, and the unset default is now hosted, which refuses without an issuer. So it passes --local and scrubs the runtime settings first.
The falsifier
F13.1's refusal probes, verbatim from # LOCAL mode REFUSES a non-loopback bind to the end of the block, plus T070's own disagreeing pair. Two deviations are forced by the base, and neither weakens a check:
the install is the working venv's -e ".[runtime,test]";
The whole sequence under set -euo pipefail (F13.1's own form), AFTER:
probe 1: local refuses a non-loopback bind
rc=1; stderr: generate-and-open refused: --host '0.0.0.0' is not a loopback address, and a LOCAL install binds LOOPBACK ONLY (127.0.0.1, ::1, localhost). … There is no opt-in: …
probe 2: load_settings (T071's block, unchanged)
probe 3: hosted with no issuer refuses naming it
rc=1; stderr: generate-and-open refused: OPENDOX_OIDC_ISSUER is required and is not set, and this install is HOSTED …
probe 4: the unset default refuses identically
rc=1; stderr: generate-and-open refused: OPENDOX_OIDC_ISSUER is required and is not set, and this install is HOSTED (OPENDOX_INSTALL_MODE is unset, and unset means hosted) …
probe 5 (T070's own, not F13.1's): a disagreeing flag and setting are refused naming both
rc=1; stderr: generate-and-open refused: --local selects the LOCAL install and OPENDOX_INSTALL_MODE=hosted selects the HOSTED one. …
F13.1 REFUSALS + T070 PAIR: ALL PASSED
In the suite, the same probes run as bounded child processes of python -m opendox.cli (timeout=30, and a TimeoutExpired fails as "a server that STARTED") in tests/test_install_mode_entrypoint.py. The loader-level cases are in tests_runtime/test_install_mode.py.
A mutant of each new refusal, killed
Each mutant was applied alone and the three T070 test files run with -x, with a 240 s bound so a hang could not pass for a kill:
M10 at first HUNG rather than failed: the un-stubbed case started a real uvicorn listener. That case now stubs uvicorn and the app, so the mutant fails at once. The table above is the re-run.
Fix round: the fixture's repository ignores the user's git config (32683e8)
Copilot's review at b50e3b1 (finding) was real. tests/test_install_mode_entrypoint.py's corpus fixture ran git commit under the caller's global git configuration, so a global commit.gpgsign=true failed the setup before any probe ran.
It now sets GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_NOSYSTEM=1, as tests/test_checkout_head.py does. Measured with a hostile global config (commit.gpgsign = true, gpg.program = /bin/false):
Fix round 2: runtime migrate and reset refuse what a local install cannot be (525f61c)
Copilot's second review, at 32683e8, raised two points in its overview, with no inline thread. Both are answered on the PR.
Real, and fixed.load_migration_settings recorded local but never asked refuse_what_a_local_install_cannot_be. So runtime migrate and a confirmed runtime reset accepted a broker setting, or a non-loopback OPENDOX_BIND_HOST, beside OPENDOX_INSTALL_MODE=local. They now refuse at configuration, before any database is reached.
Seven new cases in tests_runtime/test_install_mode.py: the three broker settings × {migrate, reset --confirm …}, plus the bind.
Against 32683e8's config they give 7 failed; here they pass.
Pre-existing, and not changed here: ::1 passes the loopback rule, but the server cannot bind it.serve.build_server is IPv4-only (ThreadingHTTPServer), while serve.LOOPBACK_HOSTS lists ::1.
This PR's rule is serve.LOOPBACK_HOSTS itself (13.4's "same judgement"). So ::1 is classified exactly as the document server already classifies it, and then fails at the bind exactly as it does in hosted mode today.
The fix is a serve.py change (address family, and server_url's brackets), outside T070's file set. It is listed below for the holder.
Fix round 3: status reports a local install's broker on its early return too (02dadc5)
Copilot's review at 525f61c raised one point in its overview, with no inline thread. It is real, and it is answered on the PR.
With the runtime extra absent, runtime status returns early, and that return said broker_keys: "not probed" for every install.
Local now gets the full report's answer there: "not configured (local mode)", with broker_discovery: null. Both returns write it through one helper.
Hosted still says "not probed" (13.6).
The new case runs with opendox.runtime.db absent from sys.modules. [local] fails against 525f61c's runtime/cli.py and passes here; [hosted] passes in both.
Four mutants of the fix are killed.
Fix round 4: a healthy local status is proven to exit 0; RuntimeSettings' invariants are scoped (859b37b6)
Copilot's review at 02dadc55 opened two threads, both real. Both are answered and resolved.
r4139922962: both local status cases forced a database fault, so nothing proved exit 0.
A DB-backed case now runs local status against a migrated schema on the suite's server and asserts ok, exit 0, and a broker that is not configured and never probed.
With the local return mutated to ok=False, the new case fails and the module's other 40 pass. Before this commit, that mutant survived.
r4139922999: the docstring's broker statements hold for load_settings only, so they are now scoped to their loader. load_migration_settings carries the migration sentinels in either shape. This is documentation only.
The repo's own suite
Full python -m pytest -q, LANG=C.UTF-8, CI=true, against a postgres:16 like validate.yml's:
026f00ea (fix round 5) is a docstring change. The install-mode module now names its one DB-backed case (r4146171212, resolved), and the module runs 41 passed.
That is +56 cases: this PR's two new files, plus the .env.example census's new parameter (+46 at b50e3b1), fix round 2's seven, fix round 3's two, and fix round 4's one. EXPECT_SKIPPED=11 holds exactly, and the floors (MIN_SELECTED=2476, MIN_PASSED=2465) allow the rise unchanged.
Downstream, for the holder
serve.py cannot bind ::1 (pre-existing, measured at f097fd8; fix round 2, item 2). LOOPBACK_HOSTS lists ::1, so --local --host ::1 passes the loopback rule and then fails at the bind with gaierror, as --host ::1 already does in hosted mode. The fix makes the serve path IPv6-aware, which is a follow-up in serve.py's single-writer order. Until then, ::1 could instead be dropped from LOOPBACK_HOSTS. That is the holder's call; this PR does neither.
The help-tree golden in openxFactory changes at the phase-3 pin.tests/ideation-dashboard/fixtures/cli-help-tree.golden.txt (read by test_extension_point_parity.py) snapshots generate-and-open's options, and --local is a new one. T094's consumer-pin PR regenerates it. openXdox-code's help-tree case (T042/T008) may need the same.
T056 lands before T070 (holder, 2026-09-30). At this stack's merge-from-main round after T056 lands, T070 updates T056's tests/test_standalone_generate_path.py, and any other generate-and-open caller that relies on the old default, to pass --local, or to use a hosted fixture where hosted is what it means. Those edits will be named here.
Add fail-closed hosted and local install-mode selection across the document-generation and runtime commands, preserving hosted behavior while enabling broker-free loopback operation.
New Features:
Add the OPENDOX_INSTALL_MODE setting and generate-and-open --local flag for explicitly selecting hosted or local operation.
Support local single-user operation without broker configuration while enforcing loopback-only binding.
Bug Fixes:
Prevent conflicting or invalid install-mode selections and broker settings from being silently accepted.
Ensure hosted runs without an issuer fail with a clear issuer-specific refusal before other configuration checks.
Prevent runtime API serving and broker probing in local mode, while reporting local status correctly.
Enhancements:
Expose the resolved install mode in runtime settings and status output, with empty broker endpoints for local installations.
Apply local-mode validation consistently to migration and reset commands.
Deployment:
Document the new install-mode setting in the deployment environment example.
Tests:
Add entrypoint and runtime coverage for install-mode selection, refusal behavior, local status and serve behavior, and hosted-mode compatibility.
Make repository fixtures independent of users' global and system Git configuration.
…n --local (plan 034)
OPENDOX_INSTALL_MODE (`local` | `hosted`, default `hosted`) is read in
runtime/config.py beside OPENDOX_OIDC_ISSUER and decides the install shape
(#1144 13.4). `generate-and-open --local` makes the same selection
(R1Q15 (b), as T007 batch H's 13.4 addendum reads); with neither the install
is hosted (13.5).
- A flag and a setting that disagree (`--local` beside
OPENDOX_INSTALL_MODE=hosted) are refused, naming both. This is plan 034's
fail-closed reading (Principle VII); no answer rules it and batch H does
not write it into #1144.
- LOCAL needs no broker: issuer, audience and key-set URL are empty.
- LOCAL binds loopback only, with no opt-in. A non-loopback `--host` or
OPENDOX_BIND_HOST is refused, naming the rule. The set is serve.py's own
LOOPBACK_HOSTS, and a test holds the two equal.
- HOSTED, set or by default, with no issuer refuses, naming
OPENDOX_OIDC_ISSUER. generate-and-open asks the issuer first, so a run with
nothing configured names it and `--local`. The hosted mode is otherwise
unchanged (13.6).
Holder readings on openxFactory#656 (Brett may overrule):
- `runtime serve` refuses under local, because the API's identity is the
broker's.
- `runtime status` under local reports broker_keys "not configured (local
mode)" and does not count it as a fault.
- A broker setting beside local is refused by name.
- An unrecognised mode value is refused, case-sensitively.
The document server's generate-and-open resolves the shape before it scans,
mints or binds anything. The hosted path loads the whole runtime
configuration (R1Q16 (i); 13.4a).
Also:
- deploy/compose/.env.example gains OPENDOX_INSTALL_MODE=hosted, which
test_every_runtime_setting_is_documented_in_env_example requires of every
SETTINGS entry.
- tests/test_doxbench_entrypoint.py's fixture now selects `--local` and
scrubs the runtime settings, since the unset default is hosted and refuses
with no issuer.
Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Implements phase-3 standalone install-mode selection: hosted remains the safe default, while explicit local mode is selected by OPENDOX_INSTALL_MODE=local or generate-and-open --local, enforced with fail-closed conflict handling, loopback-only binding, no broker requirements, and early refusals. Runtime commands and status reporting are adapted accordingly, with extensive bounded entrypoint and configuration tests plus deployment documentation.
Sequence diagram for install-mode resolution in generate-and-open
sequenceDiagram
participant User
participant CLI as generate-and-open
participant Config as runtime_config
participant Runtime as Runtime configuration
User->>CLI: generate-and-open [--local]
CLI->>Config: install_mode(env, local_flag)
alt conflicting explicit selections
Config-->>CLI: ConfigurationError
CLI-->>User: refusal and exit 1
else local mode
Config->>Config: refuse_a_non_loopback_local_bind(--host, host)
Config->>Config: refuse_what_a_local_install_cannot_be(env)
Config-->>CLI: LOCAL
else hosted or unset
Config->>Config: require_the_hosted_issuer(env)
Config->>Runtime: load_settings(env)
Runtime-->>CLI: HOSTED settings
end
CLI->>CLI: generate and serve only after resolution
Loading
File-Level Changes
Change
Details
Files
Add install-mode resolution and fail-closed selection semantics to runtime configuration and the document-generation CLI.
Introduce OPENDOX_INSTALL_MODE with exact local/hosted values and a hosted default.
Add generate-and-open --local, reject disagreement with the environment selector, and resolve the mode before scanning, generating, or serving.
Require the hosted issuer explicitly while allowing local mode to omit broker settings.
Reject broker-only settings and non-loopback binds for local installs, while preserving hosted behavior.
src/opendox/runtime/config.py src/opendox/cli.py
Make runtime service and status behavior aware of local installs without attempting broker operations.
Refuse runtime serve in local mode with a structured no-broker explanation.
Report broker configuration as unavailable in local-mode status and avoid verifier construction.
Expose the resolved install mode in redacted settings and make empty local discovery/JWKS URLs explicit.
Trigger a new review: Comment @sourcery-ai review on the pull request.
Continue discussions: Reply directly to Sourcery's review comments.
Generate a GitHub issue from a review comment: Ask Sourcery to create an
issue from a review comment by replying to it. You can also reply to a
review comment with @sourcery-ai issue to create an issue from it.
Generate a pull request title: Write @sourcery-ai anywhere in the pull
request title to generate a title at any time. You can also comment @sourcery-ai title on the pull request to (re-)generate the title at any time.
Generate a pull request summary: Write @sourcery-ai summary anywhere in
the pull request body to generate a PR summary at any time exactly where you
want it. You can also comment @sourcery-ai summary on the pull request to
(re-)generate the summary at any time.
Generate reviewer's guide: Comment @sourcery-ai guide on the pull
request to (re-)generate the reviewer's guide at any time.
Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
pull request to resolve all Sourcery comments. Useful if you've already
addressed all the comments and don't want to see them anymore.
Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
request to dismiss all existing Sourcery reviews. Especially useful if you
want to start fresh with a new review - don't forget to comment @sourcery-ai review to trigger a new review!
…it config (Copilot review)
`tests/test_install_mode_entrypoint.py`'s `corpus` fixture ran `git commit`
under the caller's global and system git configuration. A global
`commit.gpgsign=true` therefore failed the setup before any install-mode
probe ran. Measured with a hostile global config (`commit.gpgsign = true`,
`gpg.program = /bin/false`): 7 errors at b50e3b1, 14 passed here. The
fixture now sets GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_NOSYSTEM=1, as
tests/test_checkout_head.py does.
Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
::1 is accepted here (and the new tests assert that it is valid), but generate-and-open passes it to serve.build_server, which constructs the standard ThreadingHTTPServer with its default IPv4 address family. Binding --host ::1 therefore raises gaierror instead of starting the local server; serve.server_url would also need IPv6 bracket handling. Please make the server/URL path IPv6-aware, or avoid advertising ::1 as supported.
Validate local environment before migration settings construction
src/opendox/runtime/config.py:1825
This records local but skips the local-shape validation, so runtime migrate and confirmed runtime reset silently accept OPENDOX_OIDC_ISSUER, OPENDOX_OIDC_AUDIENCE, or OPENDOX_OIDC_JWKS_URL beside OPENDOX_INSTALL_MODE=local. That contradicts the new fail-closed rule applied by load_settings and generate-and-open. Validate the local environment here too before constructing the migration settings.
…ot be (Copilot review)
load_migration_settings recorded OPENDOX_INSTALL_MODE=local but never asked
refuse_what_a_local_install_cannot_be. So `runtime migrate` and a
confirmed `runtime reset` accepted OPENDOX_OIDC_ISSUER, OPENDOX_OIDC_AUDIENCE,
OPENDOX_OIDC_JWKS_URL or a non-loopback OPENDOX_BIND_HOST beside `local`,
which load_settings and generate-and-open both refuse. They now refuse them
at configuration, before any database is reached.
Seven new cases:
- the three broker settings x {migrate, reset};
- the bind.
All seven fail at 32683e8 and pass here. Full suite: 2538 selected, 2527
passed, 11 skipped, 0 failed.
Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot's review at 32683e8 raised two "previously missed" points in its overview, with no inline thread. Both are answered here.
1. Migration commands bypass the local-mode validation. Real, and fixed at 525f61c.
load_migration_settings recorded local but never asked refuse_what_a_local_install_cannot_be. So runtime migrate and a confirmed runtime reset accepted a broker setting, or a non-loopback OPENDOX_BIND_HOST, beside OPENDOX_INSTALL_MODE=local.
It now refuses them at configuration, before any database is reached.
Seven new cases (the three broker settings × {migrate, reset --confirm …}, plus the bind): 7 failed against 32683e8's config, and all pass here.
Full suite: 2538 selected, 2527 passed, 11 skipped, 0 failed.
2. ::1 is accepted, but the server cannot bind it. That is pre-existing in serve.py, and it is not changed here.
That is because ThreadingHTTPServer is IPv4, while serve.LOOPBACK_HOSTS lists ::1.
This PR's local rule is deliberately serve.LOOPBACK_HOSTS itself. 13.4 asks for "the same judgement at the mode's own boundary", and a test holds the two sets equal. So ::1 passes the loopback rule, exactly as the document server already classifies it, and then fails at the bind exactly as it does in hosted mode today.
Making the serve path IPv6-aware (address family, and server_url's brackets) is a serve.py change, and serve.py is not in T070's file set or single-writer slot. It is recorded for the holder rather than done around the plan.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🔵 Needs a closer look
Local status reports the wrong broker state when the runtime extra is unavailable.
Review effort: Balanced Findings: None
Previously missed (1)
In code that hasn't changed since last review
Populate local broker status before runtime dependency early return
src/opendox/runtime/cli.py:783
The local-mode branch runs after the existing runtime-extra import at lines 694–700. If psycopg is absent, cmd_status returns there with broker_keys: "not probed" and no broker_discovery, so local status does not consistently report the promised “not configured (local mode)”/null broker state. Populate the local broker fields before that early return (while still skipping build_verifier later).
…n too (Copilot review)
When the runtime extra is absent, `runtime status` returns early, and that
return said `broker_keys: "not probed"` for every install. A local install's
broker is not configured whether or not the extra is present. That answer
comes from the configuration, not from a probe, so the early return now gives
the local install the answer the full report gives: `"not configured (local
mode)"`, with `broker_discovery: null`. Both returns write it through one
helper, so the two cannot drift. A hosted install's early return still reads
"not probed", as before (13.6).
The branch is covered now, so its `pragma: no cover` goes. A new case runs
both shapes with `opendox.runtime.db` absent from `sys.modules`. Before
(`525f61c`'s runtime/cli.py): local 1 failed and hosted passed. After: both
pass. Four mutants of the fix are killed. Full suite: 2540 selected, 2529
passed, 11 skipped, 0 failed.
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Arc: neutral-product-standalone-operability
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🔵 Needs a closer look
The healthy local-status exit path lacks regression coverage proving that an absent broker does not cause failure.
Review effort: Balanced Findings: None
Previously missed (1)
In code that hasn't changed since last review
Add healthy-local status test to verify exit code 0
tests_runtime/test_install_mode.py:321
This case cannot verify the promised healthy-local exit code: port 1 guarantees the database path has already set ok = False, so a regression that also treats the intentionally absent broker as unhealthy would still satisfy code == 1. Add a local-status case with a successful/stubbed database and migration probe and assert exit code 0; that is the path needed to prove status survives set -e when only the broker is absent.
Copilot's review at 525f61c raised one "previously missed" point in its overview, with no inline thread: "Populate local broker status before runtime dependency early return", at src/opendox/runtime/cli.py:783.
It is real, and it is fixed at 02dadc5.
The runtime extra's ImportError return said broker_keys: "not probed" for every install.
A local install's broker is not configured whether or not the extra is present. That answer comes from its configuration, not from a probe, so that return now gives local the full report's answer: "not configured (local mode)", with broker_discovery: null.
Both returns write it through one helper (_report_the_local_broker). A hosted install's early return still says "not probed" (13.6). ok is still false there, because the database was not probed.
The branch is covered now, so its pragma: no cover goes.
Measured
New case test_runtime_status_without_the_runtime_extra_reports_the_broker_by_mode[local|hosted], with opendox.runtime.db set to None in sys.modules.
Against 525f61c's runtime/cli.py: [local] fails and [hosted] passes.
Here: both pass.
Four mutants are killed: local reads "not probed"; hosted gets the local answer; the helper omits broker_discovery; the early return counts as ok.
Full suite: 2540 selected, 2529 passed, 11 skipped, 0 failed.
…tings' broker invariants are scoped (Copilot review)
A local `status` returns `ok` on the database's verdict alone. Both earlier
local cases forced a database fault and asserted exit 1, so a regression
that also counted the absent broker as a fault would still have passed. A
DB-backed case now runs `status` for a local install against a migrated
schema on the suite's own server (`database` and `postgres_dsn`, with the
schema selected in the DSN). It asserts `ok` true, exit 0, the database
reachable with nothing pending and no drift, and the broker reported as not
configured and never probed. Measured: with the local return mutated to
`ok=False`, this case fails and the other 40 in the module pass.
`RuntimeSettings`' docstring said that a local install's issuer and audience
are empty and that a hosted one always carries a real issuer. That is true of
`load_settings` alone. `load_migration_settings` carries the migration
sentinels in either shape. The docstring now scopes each statement to its
loader.
Full suite: 2541 selected, 2530 passed, 11 skipped, 0 failed.
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Arc: neutral-product-standalone-operability
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ed (Copilot review)
The module docstring called every case hermetic. Since the fourth fix round,
one is not: `test_runtime_status_of_a_healthy_local_install_exits_zero` takes
the suite's `postgres_dsn` and `database` fixtures, because a healthy local
`status` exits 0 only against a database that answers. The docstring now
names that case and says it is skipped without Postgres and fails under CI,
like every DB-backed case. It says the rest stay hermetic. Docstring only:
the module runs 41 passed.
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Arc: neutral-product-standalone-operability
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Arc: neutral-product-standalone-operability
Plan 034 (
specs/034-opendox-standalone-operation/tasks.md, read at openxFactorymain91e4685f, after T007 batch H landed as openxFactory#1206 →f99a2097), phase-3 slice P3-I, install mode and the bundle:OPENDOX_INSTALL_MODE, and the--localflag.Ruled:
5817152735;5850003126(as batch H's 13.4 addendum reads);5901112350("Install chain + lens (Recommended)").Claimed on openxFactory#656 in
5901575394. DRAFT, authored ahead: it does not go READY before T063 lands and the holder says so.What it does
OPENDOX_INSTALL_MODE, valueslocalandhosted, defaulting tohosted. It is aSETTINGSentry read inruntime/config.py, placed immediately besideOPENDOX_OIDC_ISSUER. A blank value reads as unset, which means hosted: "It is UNSET, notlocal, that must be safe."generate-and-open --localselects local exactly asOPENDOX_INSTALL_MODE=localdoes. With neither, the install is hosted (13.5). The flag belongs to the verb and follows it (10.1).--localbesideOPENDOX_INSTALL_MODE=hosted. No explicit selection is silently overridden. This is plan 034's fail-closed reading (Principle VII), not #1144's text. No answer rules the pair, batch H does not write it into #1144, andevidence/analyze-round-2.md(U2-1, V2-6) records it for Brett.RuntimeSettings.oidc_issuerandoidc_audienceare empty, andoidc_jwks_urlisNone.jwks_url()anddiscovery_url()return""rather than a path glued onto nothing.--hostongenerate-and-open, orOPENDOX_BIND_HOSTfor the runtime's own listener, is refused, naming the loopback rule.serve.py's ownLOOPBACK_HOSTS(127.0.0.1,::1,localhost). 13.4 asks for "the same judgement at the mode's own boundary".configcannot importserve, so it spells the set, and a test holds the two equal.127.0.0.2is therefore refused, because the document server does not treat it as loopback either.OPENDOX_OIDC_ISSUER(13.5).generate-and-openasks for the issuer first (require_the_hosted_issuer), and then loads the whole runtime configuration. The serving process is the one whose settings are the install's (13.4a; R1Q16 (i)).--local, not theOPENDOX_DATABASE_URLthatload_settingshappens to ask for first (plan 034's requirement-13 scenario 2).load_settingskeeps its own order for every verb that relies on it.0.0.0.0.generate-and-openresolves it before it scans, mints or binds anything, so each refusal exits 1 at once. That covers F13.1'stest "$rc" -ne 124.Holder readings (coordinator, 2026-09-30, on openxFactory#656; Brett may overrule)
opendox-runtime runtime serverefuses underlocal(refusal: local-mode-has-no-broker). Every/api/v1route verifies a broker-signed token, and a local install is served bygenerate-and-open --local. In release 1 its document surface reads nothing from the store (R1Q16 (ii)).runtime statusunderlocalreportsbroker_keys: "not configured (local mode)"andbroker_discovery: null. It never builds a verifier, and its exit code is the database's verdict alone, so F13.1'sset -esurvives.localis refused, naming each one:OPENDOX_OIDC_ISSUER,OPENDOX_OIDC_AUDIENCEandOPENDOX_OIDC_JWKS_URL. The values are never repeated. T072 adds the two DSNs to the list.Local,single-user…) is refused, naminglocalandhosted, and matching is case-sensitive.Deliberately NOT here
local,load_settingsstill takes the DSNs from the environment, andgenerate-and-open --localloads no database setting. T072 supplies both DSNs from the bundled server (13.1) and refuses operator DSNs besidelocal.installblock is not here.serve.pyandpyproject.tomlare untouched.Outside
src/andtests/deploy/— one line, and the task requires it.deploy/compose/.env.examplegainsOPENDOX_INSTALL_MODE=hostedwith its comment.config.py's header makes.env.examplethe place every setting is named, andtests_runtime/test_deploy_shape.py::test_every_runtime_setting_is_documented_in_env_exampleis parametrized overSETTINGS, so a new setting without the line is red. Neither the compose file nor the Kubernetes base changes: the default is already hosted.docs/: untouched. 10.3's README line belongs to T076, in the openDox root.tests/test_doxbench_entrypoint.py's fixture. It drivescmd_generate_and_openwith no settings, and the unset default is now hosted, which refuses without an issuer. So it passes--localand scrubs the runtime settings first.The falsifier
F13.1's refusal probes, verbatim from
# LOCAL mode REFUSES a non-loopback bindto the end of the block, plus T070's own disagreeing pair. Two deviations are forced by the base, and neither weakens a check:-e ".[runtime,test]";tests/fixtures/plain-documentsarrives with T050 (T050, the plain-documents fixture, spread across the six stations #53), which this stack's base (main2d116415) predates.Each probe on its own. BEFORE is #60's head
f097fd8; AFTER is this branch:The whole sequence under
set -euo pipefail(F13.1's own form), AFTER:In the suite, the same probes run as bounded child processes of
python -m opendox.cli(timeout=30, and aTimeoutExpiredfails as "a server that STARTED") intests/test_install_mode_entrypoint.py. The loader-level cases are intests_runtime/test_install_mode.py.A mutant of each new refusal, killed
Each mutant was applied alone and the three T070 test files run with
-x, with a 240 s bound so a hang could not pass for a kill:test_a_flag_and_a_setting_that_disagree_are_refused_naming_bothtest_an_unrecognised_value_is_refused_naming_the_two[Local][Local]test_the_selector_has_two_values_and_its_default_is_hostedtest_a_hosted_install_with_no_issuer_refuses_naming_it[None]test_a_local_install_refuses_a_non_loopback_bind_naming_the_rule[0.0.0.0]test_a_broker_setting_beside_the_local_mode_is_refused_by_name[OPENDOX_OIDC_ISSUER]127.0.0.2)test_a_local_install_refuses_a_non_loopback_bind_naming_the_rule[127.0.0.2]test_a_local_install_needs_no_broker[setting]runtime serveserves under localtest_runtime_serve_refuses_under_the_local_moderuntime statusprobes a broker under localtest_runtime_status_under_the_local_mode_probes_no_brokergenerate-and-openignores--localtest_the_flag_refuses_a_non_loopback_bind_exactly_as_the_setting_doesgenerate-and-openskips the install shapetest_local_mode_refuses_a_non_loopback_bind_naming_the_ruleM10 at first HUNG rather than failed: the un-stubbed case started a real uvicorn listener. That case now stubs uvicorn and the app, so the mutant fails at once. The table above is the re-run.
Fix round: the fixture's repository ignores the user's git config (
32683e8)Copilot's review at
b50e3b1(finding) was real.tests/test_install_mode_entrypoint.py'scorpusfixture rangit commitunder the caller's global git configuration, so a globalcommit.gpgsign=truefailed the setup before any probe ran.It now sets
GIT_CONFIG_GLOBAL=/dev/nullandGIT_CONFIG_NOSYSTEM=1, astests/test_checkout_head.pydoes. Measured with a hostile global config (commit.gpgsign = true,gpg.program = /bin/false):b50e3b1:7 passed, 7 errors;32683e8:14 passed.Replied. The probes are unchanged.
Fix round 2:
runtime migrateandresetrefuse what a local install cannot be (525f61c)Copilot's second review, at
32683e8, raised two points in its overview, with no inline thread. Both are answered on the PR.load_migration_settingsrecordedlocalbut never askedrefuse_what_a_local_install_cannot_be. Soruntime migrateand a confirmedruntime resetaccepted a broker setting, or a non-loopbackOPENDOX_BIND_HOST, besideOPENDOX_INSTALL_MODE=local. They now refuse at configuration, before any database is reached.tests_runtime/test_install_mode.py: the three broker settings × {migrate,reset --confirm …}, plus the bind.32683e8's config they give7 failed; here they pass.::1passes the loopback rule, but the server cannot bind it.serve.build_serveris IPv4-only (ThreadingHTTPServer), whileserve.LOOPBACK_HOSTSlists::1.f097fd8, before this PR:127.0.0.1binds, and::1fails withgaierror [Errno -9] Address family for hostname not supported.serve.LOOPBACK_HOSTSitself (13.4's "same judgement"). So::1is classified exactly as the document server already classifies it, and then fails at the bind exactly as it does in hosted mode today.serve.pychange (address family, andserver_url's brackets), outside T070's file set. It is listed below for the holder.Fix round 3:
statusreports a local install's broker on its early return too (02dadc5)Copilot's review at
525f61craised one point in its overview, with no inline thread. It is real, and it is answered on the PR.runtime statusreturns early, and that return saidbroker_keys: "not probed"for every install."not configured (local mode)", withbroker_discovery: null. Both returns write it through one helper."not probed"(13.6).opendox.runtime.dbabsent fromsys.modules.[local]fails against525f61c'sruntime/cli.pyand passes here;[hosted]passes in both.Fix round 4: a healthy local
statusis proven to exit 0;RuntimeSettings' invariants are scoped (859b37b6)Copilot's review at
02dadc55opened two threads, both real. Both are answered and resolved.statuscases forced a database fault, so nothing proved exit 0.statusagainst a migrated schema on the suite's server and assertsok, exit 0, and a broker that is not configured and never probed.ok=False, the new case fails and the module's other 40 pass. Before this commit, that mutant survived.load_settingsonly, so they are now scoped to their loader.load_migration_settingscarries the migration sentinels in either shape. This is documentation only.The repo's own suite
Full
python -m pytest -q,LANG=C.UTF-8,CI=true, against apostgres:16likevalidate.yml's:f097fd8b50e3b1(T070)525f61c(fix round 2)02dadc5(fix round 3)859b37b6(fix round 4)026f00ea(fix round 5) is a docstring change. The install-mode module now names its one DB-backed case (r4146171212, resolved), and the module runs 41 passed.That is +56 cases: this PR's two new files, plus the
.env.examplecensus's new parameter (+46 atb50e3b1), fix round 2's seven, fix round 3's two, and fix round 4's one.EXPECT_SKIPPED=11holds exactly, and the floors (MIN_SELECTED=2476,MIN_PASSED=2465) allow the rise unchanged.Downstream, for the holder
serve.pycannot bind::1(pre-existing, measured atf097fd8; fix round 2, item 2).LOOPBACK_HOSTSlists::1, so--local --host ::1passes the loopback rule and then fails at the bind withgaierror, as--host ::1already does in hosted mode. The fix makes the serve path IPv6-aware, which is a follow-up inserve.py's single-writer order. Until then,::1could instead be dropped fromLOOPBACK_HOSTS. That is the holder's call; this PR does neither.tests/ideation-dashboard/fixtures/cli-help-tree.golden.txt(read bytest_extension_point_parity.py) snapshotsgenerate-and-open's options, and--localis a new one. T094's consumer-pin PR regenerates it. openXdox-code's help-tree case (T042/T008) may need the same.tests/test_standalone_generate_path.py, and any othergenerate-and-opencaller that relies on the old default, to pass--local, or to use a hosted fixture where hosted is what it means. Those edits will be named here.gh pr diff -R opensoft/openDox-code):src/opendox/cli.pyis also rewritten by T055, serve and generate standalone (5.5, 4.3 part) (plan 034) #59 (T055, +260/−112), incmd_generate_and_openandbuild_parser.cli.py's single-writer order is T055 → T058 → T070. This stack takes its merge-from-main round after T054, openDox's small neutral projection (5.1-5.3) (plan 034) #57, T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58 and T055, serve and generate standalone (5.5, 4.3 part) (plan 034) #59 land, and edits around none of them. T054, openDox's small neutral projection (5.1-5.3) (plan 034) #57 and DRAFT (phase 3, after T063): T078, 16.1: the OpenAI-compatible dialect joins DIALECTS (plan 034) #61–DRAFT (phase 3, after T063): broker-path credential hardening (follows T080): a minted token keeps a built-in credential's rules (plan 034) #64 do not touch these files.🤖 Generated with Claude Code
Summary by Sourcery
Add fail-closed hosted and local install-mode selection across the document-generation and runtime commands, preserving hosted behavior while enabling broker-free loopback operation.
New Features:
OPENDOX_INSTALL_MODEsetting andgenerate-and-open --localflag for explicitly selecting hosted or local operation.Bug Fixes:
Enhancements:
Deployment:
Tests: