Skip to content

feat(miner): wire AMS's hosted entry point to the #8202 tenant-secret bootstrap#8263

Merged
JSONbored merged 1 commit into
mainfrom
feat/ams-tenant-secret-wiring
Jul 23, 2026
Merged

feat(miner): wire AMS's hosted entry point to the #8202 tenant-secret bootstrap#8263
JSONbored merged 1 commit into
mainfrom
feat/ams-tenant-secret-wiring

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Closes #8246 -- the AMS half of #8202/#8253's hosted tenant-secret bootstrap mechanism.

OrbTenantContainer gets that mechanism close to free because it runs the unmodified self-host image, which already imports src/orb/broker-client.ts. AmsTenantContainer's image (packages/loopover-miner) had zero broker-aware code -- this PR adds it.

Cross-package import isn't possible here (confirmed empirically, not assumed): packages/loopover-miner/tsconfig.json scopes "rootDir": "." to the package itself, so a relative import reaching into root src/orb/broker-client.ts resolves outside rootDir and fails tsc with TS6059. This matches an already-established convention in this codebase for the exact same package boundary -- control-plane/src/secret-driver.ts's own header comment duplicates a constant "rather than imported" for an identical reason, and control-plane/src/http-app.ts's HOSTED_CYCLE_COMMANDS comment explicitly cross-references this file as the same case. So packages/loopover-miner/lib/tenant-credential-resolution.ts duplicates the exchange logic rather than importing it -- same URL-safety validation, same POST /v1/orb/token call shape, same response parsing as fetchBrokeredStoredSecret.

Scope check before writing this: I verified there is currently no consumer anywhere in packages/loopover-miner for a resolved database connection string -- the miner's own stores are unconditionally local SQLite (store-db-adapter.ts's own comment: AMS can "later" swap in a Postgres adapter, hasn't happened), and the existing attempt-db-fork-config.ts (#7858's Neon branch-per-attempt config) is a completely separate, unrelated concept (a disposable per-attempt fork of some other database, not the tenant's own persistent one). This exactly mirrors ORB's own situation -- #8202/#8253 shipped the mechanism and the exchange function but never wired a runtime consumer into src/server.ts either, deliberately deferring that. So resolveTenantSecret is a best-effort wrapper (unlike the throwing fetchTenantSecret/ORB's fetchBrokeredStoredSecret): a broker outage or unconfigured token must not block a scheduled discover/manage-poll/attempt cycle from running when nothing consumes the result yet.

Naming note: the new file is tenant-credential-resolution.ts, not tenant-secret-resolution.ts -- scripts/check-miner-package.ts's FORBIDDEN_PATH filter has a coarse .*secret.* filename heuristic (aimed at stray files like .env/.pem, not descriptively-named source code) that the original name tripped. Renaming was more surgical than loosening a security-relevant filter, and "credential" already matches this codebase's own established term for the same concept (ORB_SECRET_TYPE_TENANT_DB_CREDENTIAL). The exported symbols (fetchTenantSecret, resolveTenantSecret, TenantSecret) keep "Secret" in their names, matching broker-client.ts's own naming for the function this duplicates.

Where it's wired

hosted-entry.ts's runHostedEntry calls resolveTenantSecret(env) once per wake, right after the known-cycle-name check and before the health server starts, and logs the outcome (ams_hosted_entry_tenant_secret_resolved, resolved/secretType). Never blocks or changes cycle dispatch either way.

Tests

  • test/unit/miner-tenant-credential-resolution.test.ts: mirrors test/unit/orb-broker-client.test.ts's own fetchBrokeredStoredSecret coverage (success, custom broker URL, unsafe-URL rejection, localhost dev exception, both defensive ?? branches, non-OK response, missing secretValue) plus resolveTenantSecret's best-effort behavior (no token -> null without calling fetch, blank token -> same, success passes through, exchange failure is swallowed + warn-logged + returns null, including a non-Error rejection).
  • test/unit/miner-hosted-entry.test.ts: new describe block proving resolveTenantSecret is called with the right env, never blocks dispatch on either outcome, the resolved/omitted log line is correct, and it's never called at all on the early-return (unknown cycle name) path.

Verified: full unsharded npm run test:coverage (21030/21051 passing, only pre-existing skips, 0 failures -- run twice, once before and once after a fix described below), tsc --noEmit --incremental false clean, npm run build:miner clean, npm run test:miner-deployment-docs-audit clean (the new file never reads process.env directly, only via a passed-in env param, so it's outside that audit's static-analysis scope), git diff --check clean, real c8-scoped coverage 100% line+branch on both touched/new files.

Found and fixed along the way: my first pass named the file tenant-secret-resolution.ts, which failed test/unit/check-miner-package.test.ts > passes on the real miner workspace package -- scripts/check-miner-package.ts's filename-based secret filter caught it. Renamed rather than touching the filter itself (see Naming note above); the full suite re-run clean afterward.

Test plan

  • npm run test:coverage (full unsharded, 21030/21051 passing, 0 failures)
  • tsc --noEmit --incremental false
  • npm run build:miner
  • npm run test:miner-deployment-docs-audit
  • npx tsx scripts/check-miner-package.ts (real pack dry-run)
  • git diff --check

… bootstrap (#8246)

Adds tenant-credential-resolution.ts, a duplicated (not imported -- cross-
package import from packages/loopover-miner into root src/ fails tsc with
TS6059, confirmed) exchange of LOOPOVER_TENANT_SECRET_TOKEN against the
broker, mirroring src/orb/broker-client.ts's fetchBrokeredStoredSecret for
ORB. hosted-entry.ts resolves it once per wake, best-effort -- no code in
this package consumes the resolved value yet (the miner's stores are
unconditionally local SQLite), so this proves the mechanism is wired for AMS
without making a scheduled cycle fragile against an unused value, mirroring
#8202/#8253's own "prove it works, defer consumption" precedent for ORB.
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@JSONbored JSONbored self-assigned this Jul 23, 2026
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.62%. Comparing base (8e34212) to head (6b0b80a).
⚠️ Report is 5 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8263      +/-   ##
==========================================
- Coverage   92.14%   91.62%   -0.52%     
==========================================
  Files         785      786       +1     
  Lines       78715    78749      +34     
  Branches    23759    23773      +14     
==========================================
- Hits        72531    72154     -377     
- Misses       5062     5524     +462     
+ Partials     1122     1071      -51     
Flag Coverage Δ
shard-1 53.29% <94.11%> (+1.82%) ⬆️
shard-2 49.56% <0.00%> (-6.27%) ⬇️
shard-3 56.71% <5.88%> (+1.85%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/loopover-miner/lib/hosted-entry.ts 100.00% <100.00%> (ø)
...loopover-miner/lib/tenant-credential-resolution.ts 100.00% <100.00%> (ø)

... and 2 files with indirect coverage changes

@JSONbored
JSONbored merged commit 904ec7d into main Jul 23, 2026
14 checks passed
@JSONbored
JSONbored deleted the feat/ams-tenant-secret-wiring branch July 23, 2026 16:39
JSONbored added a commit that referenced this pull request Jul 23, 2026
…th "secret" (#8266)

check-miner-package.ts's FORBIDDEN_PATH filter matched the keyword "secret"
(or "private...key") anywhere in a path, including inside legitimately-named
.js/.ts source files -- caught while shipping PR #8263, whose
tenant-secret-resolution.ts had to be renamed to tenant-credential-resolution.ts
to work around it.

Splits the filter: exact-name dotfiles (.env/.npmrc/.dev.vars) stay forbidden
regardless of extension, and the keyword heuristic now only applies to
non-source files. Source files are exempt because a real leaked secret VALUE
is already caught unconditionally by the separate FORBIDDEN_CONTENT scan
regardless of filename -- the keyword check was only ever a coarse,
filename-based backstop for stray credential-shaped files like .pem/.json.
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.

AMS container-side wiring for the hosted tenant-secret bootstrap mechanism

1 participant