Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5138328
chore(#470): delete the dev-platform backend tree, routers and tests …
Weegy Aug 20, 2026
97a91e3
chore(#470): delete the dev-runner shim, sidecars and compose topolog…
Weegy Aug 20, 2026
d658f76
chore(#470): unwire the dev platform from index.ts, config and env (C10)
Weegy Aug 20, 2026
3f8bc7f
chore(#470): delete the dev-platform web-ui surface and its i18n keys…
Weegy Aug 20, 2026
3cf3d17
chore(#470): drop the dev-runner CI matrix and supply chain from core…
Weegy Aug 20, 2026
af131b3
fix(#470): restore four core config keys the C10 cut swallowed (C10)
Weegy Aug 20, 2026
924f486
docs(#470): record the C10 flip — ratchet baseline, specs, changelog …
Weegy Aug 20, 2026
2c6cc20
fix(#470): lock the conductor raw-body mount order and drop the dead …
Weegy Aug 20, 2026
d55a468
feat(#470): delete the two dev-platform publicPaths exemptions (C12)
Weegy Aug 20, 2026
86369dd
feat(#470): drive the core-decoupling count to 0 and pin it there (C13)
Weegy Aug 20, 2026
f7acf93
fix(#470): fail loud in CI when the closed-set 401 half cannot bind l…
Weegy Aug 20, 2026
d934660
Merge branch 'feat/470-c12-public-paths' into feat/470-c13-residue
Weegy Aug 20, 2026
04fc105
fix(#470): anchor the detector self-exclusion and finish the neutral …
Weegy Aug 20, 2026
323a994
merge origin/main (C10 #804) into feat/470-c12-public-paths
Weegy Aug 21, 2026
767bca1
chore(470): lower decoupling baseline 211 → 206 after C12
Weegy Aug 21, 2026
aa79f05
merge origin/main (C11 #806) into feat/470-c12-public-paths; dedupe b…
Weegy Aug 21, 2026
5f6e89d
merge feat/470-c12-public-paths (main incl. C10/C11) into feat/470-c1…
Weegy Aug 21, 2026
322afd1
chore(470): keep C13's neutral tombstone wording after merging C12/main
Weegy Aug 21, 2026
a73480d
merge origin/main (C12 #807) into feat/470-c13-residue
Weegy Aug 21, 2026
79de4b5
docs(470): drop the superseded in-flight ratchet row
Weegy Aug 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 37 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ permissions:

jobs:
# ------------------------------------------------------------------
# Core-decoupling ratchet (epic #470). The Dev Platform is being
# extracted into its own repository; this fails if core re-acquires
# references to it. The count may fall freely and never rise without
# a hand-edited baseline, so the extraction cannot silently regress
# while it is in flight — and "finished" is machine-checked (count 0)
# rather than asserted.
# Core-decoupling check (epic #470). The Dev Platform lives in its own
# repository; this fails if core re-acquires ANY reference to it.
#
# This was a ratchet — a committed count allowed to fall and never rise —
# for as long as the extraction was in flight. C13 finished the job and the
# floor is now hard zero: the script asserts 0 outright instead of reading
# a baseline, because a baseline is a number and numbers get edited. The
# job name is kept for continuity as a required check.
# ------------------------------------------------------------------
decoupling:
name: core decoupling ratchet (#470)
Expand Down Expand Up @@ -66,8 +68,35 @@ jobs:
|| (sudo apt-get update && sudo apt-get install -y ripgrep)
fi

- name: Check core is not re-coupling to the Dev Platform
run: node scripts/check-core-decoupling.mjs
- name: Assert core has ZERO Dev Platform references
# C13 pinned the count at 0 permanently. The script asserts that
# outright (`EXTRACTION_COMPLETE`) rather than comparing against
# `decoupling-baseline.json`, so a single reintroduced reference —
# code path, config key, i18n key, fixture string or comment — fails
# this job, and there is no number anyone can edit to make it pass.
#
# Belt and braces on top of the script's own exit code: assert the
# report literally says zero. If a future refactor ever made the
# script exit 0 while still counting hits (a `--report`-style path
# slipping into the default mode, an early `process.exit(0)`), this
# step still fails. A guard that can only fail loudly is the point.
run: |
node scripts/check-core-decoupling.mjs
node scripts/check-core-decoupling.mjs --report | tee /tmp/decoupling.txt
grep -qE '^ TOTAL 0( |$)' /tmp/decoupling.txt

- name: Test the detector itself
# The guard above only earns its required-check status if the detector
# it runs actually detects. `check-core-decoupling.test.mjs` plants a
# probe file under `middleware/src/` and asserts the checker goes red —
# it is the mutation test, committed.
#
# It is wired HERE deliberately. `scripts/` has no package.json and no
# other job globs `*.test.mjs`, so a test file sitting there would
# never execute: green because nothing ran it, which this job's own
# timeout comment above already names as the same failure family as a
# permanently-green guard-skip. A test nobody runs is not a test.
run: node --test scripts/check-core-decoupling.test.mjs

# ------------------------------------------------------------------
# Middleware: lint + typecheck + node:test against tsx
Expand Down
5 changes: 5 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ entry. See `CONTRIBUTING.md` § Releases & changelog.

## [Unreleased]

### Fixed — core-decoupling zero floor no longer hides same-named files (#470 C13 review)

- `scripts/check-core-decoupling.mjs` now excludes only the exact detector path `scripts/check-core-decoupling.mjs` instead of any basename match, closing the hole where a same-named file dropped under `middleware/src/` could hide Dev Platform identifiers from the permanent zero floor. A colocated regression test proves the detector stays self-excluded while a probe file at `middleware/src/__probe/check-core-decoupling.mjs` is counted.
- The remaining human-readable fixture labels left behind by the C13 identifier rename now use the neutral example-plugin naming too (`Example Plugin` / `Beispiel-Plugin`), so the tests assert against the strings their fixtures actually define and no permanently-green "old assertion, new fixture" trap remains.
- `middleware/test/auth/staticPublicPathsClosedSet.test.ts` still skips the loopback-listener half in restrictive local sandboxes, but if `CI` is set the same bind failure now throws with a clear message instead of silently skipping the five 401 assertions.
### Added — migration handoff: a plugin can adopt an existing installation's schema (#470 C11)

- **Plugins extracted out of core no longer re-apply core's migrations.**
Expand Down
4 changes: 2 additions & 2 deletions middleware/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ VAULT_KEY=

# --- Dev platform -----------------------------------------------------------
# The Dev Platform is no longer part of core. It ships as an installable plugin
# from https://github.com/byte5ai/omadia-dev-platform — install it via the Hub
# (or a ZIP upload) and configure it through the plugin's own settings rather
# — install it from the plugin hub at https://hub.omadia.ai (or by ZIP upload)
# and configure it through the plugin's own settings rather
# than through middleware environment variables. Every key that used to live
# here is now a plugin setting; none of them are read by the middleware.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
* the gate by re-activating the whole knowledge-graph plugin. That path calls
* `plugin.ts`'s `close()`, which calls `graphPool.end()` — and the kernel
* captured that pool ONCE (`middleware/src/index.ts`) and shares the reference
* with ~40 subsystems: routines, dev-platform webhooks, agent schedules, cost
* with ~40 subsystems: routines, plugin webhooks, agent schedules, cost
* telemetry, MCP audit, `AgentGraphStore`, `McpConfigService`. Every one of
* them answered `Cannot use a pool after calling end on the pool` after a
* SUCCESSFUL switch, until the process was restarted. A feature whose entire
Expand Down
31 changes: 16 additions & 15 deletions middleware/packages/plugin-api/src/pluginContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1726,23 +1726,24 @@ export interface McpAccessor {
}

// ---------------------------------------------------------------------------
// Dev-platform access (`ctx.devJobs`) — REMOVED.
// A DORMANT CAPABILITY WAS REMOVED HERE.
//
// `ctx.devJobs` and its six types (DevJobKind, DevJobStatus, DevJobDescriptor,
// DevJobCreateRequest, DevJobEventRecord, DevJobsAccessor) used to live here.
// Nothing ever provided the backing `'devJobs'` host service, so the accessor
// threw on every invocation, and no manifest in this repo, in the private byte5
// plugin set, or in any sibling repo ever declared `permissions.devJobs`.
// Deleted per `specs/470-dev-platform-plugin/dormant-capabilities.md` §2.
// One accessor and its six types used to sit at this point in the file. The
// backing host service was never provided by anything, so the accessor threw on
// every invocation, and no manifest in this repo, in the private byte5 plugin
// set, or in any sibling repo ever declared its permission. It was surface
// area that only looked like a contract. The exact names are listed once, in
// `packages/plugin-api/CHANGELOG.md`, so a consumer grepping its own source for
// a removed type lands on the entry that explains where it went. Per epic
// #470 (see the spec set under `specs/`) the subsystem that would have used it
// now lives in its own repository and defines these types for itself.
//
// The descriptor/event view types survive CORE-LOCALLY in
// `middleware/src/devplatform/devJobTypes.ts` for the chat dev-job surface,
// which is a host-internal consumer and never crossed this package boundary.
//
// Back-compat: a stale manifest that still declares `permissions.devJobs`
// installs and activates unchanged — unknown permission keys are ignored by
// `adaptManifestV1` and `ctx.devJobs` is simply absent (it was already
// unusable). Regression-tested in `test/manifestDevJobsLegacyKey.test.ts`.
// The reason this is a comment and not just a deletion: a plugin that still
// declares the retired permission installs and activates UNCHANGED. Unknown
// permission keys are ignored by `adaptManifestV1` and the accessor is simply
// absent (it was already unusable). That is the property that makes a
// capability removable at all, and it is regression-tested in
// `test/manifestRetiredPermissionKey.test.ts`.
// ---------------------------------------------------------------------------

export interface LlmCompleteResult {
Expand Down
13 changes: 9 additions & 4 deletions middleware/src/auth/publicPaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
* handler is ever reached.
*
* This list lives in its own module for one reason: tests must assert against
* the SAME array production runs. Epic #470's runner router was mounted without
* a session guard and its e2e test built a bare express() app to prove it — so
* the test passed while `/api/v1/dev-runner` 401'd in production behind the
* blanket guard. A shared constant makes that class of drift impossible.
* the SAME array production runs. A plugin router once shipped without a
* session guard and its e2e test built a bare express() app to prove it — so
* the test passed while the route 401'd in production behind the blanket
* guard. A shared constant makes that class of drift impossible.
*
* The list is CLOSED and core-owned: every entry is justified in
* `test/auth/staticPublicPathsClosedSet.test.ts`, and a plugin that needs a
* public path declares it in `permissions.public_paths` and has the operator
* consent to it (`platform/publicPathGrants.ts`) instead of being added here.
*/

import { CIMD_METADATA_PATH } from '../services/mcpCimd.js';
Expand Down
8 changes: 4 additions & 4 deletions middleware/src/conductor/awaitStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ export class ConductorAwaitStore {
*
* Every await IS a human await by construction: `openHumanAwait` is the single caller of
* {@link create}, and it always writes a human principal and a human channel. An earlier
* `AND channel_type <> 'dev_job'` filter hid the never-built dev-job step's synthetic awaits;
* that step was deleted with its writer, so the excluded set is now empty and the filter is
* gone rather than kept as a generic one — a filter whose complement no member can enter is
* an invariant asserted in the wrong place, and a future non-human await kind would have to
* `channel_type` exclusion filter hid a machine step's synthetic awaits; that step was
* deleted with its writer, so the excluded set is now empty and the filter is gone rather
* than kept as a generic one — a filter whose complement no member can enter is an
* invariant asserted in the wrong place, and a future non-human await kind would have to
* remember to add itself to survive it. If such a kind ever lands, it names itself here.
*/
async listWaiting(limit = 100): Promise<ConductorAwait[]> {
Expand Down
4 changes: 2 additions & 2 deletions middleware/src/conductor/migrations/0007_webhooks.sql
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ CREATE TABLE IF NOT EXISTS conductor_webhook_endpoints (
-- Scoping the key per-endpoint fixes that while keeping the same dedupe semantics: a
-- redelivery of the SAME endpoint's id is still a no-op; a caller that never sends
-- the header still gets a server-generated id recorded here (no dedupe, but no
-- silent drop either — mirrors the terminal-outcome ledger `dev_webhook_deliveries`
-- established in Epic #470 W4).
-- silent drop either — mirrors the terminal-outcome delivery ledger established
-- for outbound webhooks in Epic #470 W4).
CREATE TABLE IF NOT EXISTS conductor_webhook_inbound_deliveries (
delivery_id TEXT NOT NULL,
endpoint_id TEXT NOT NULL,
Expand Down
12 changes: 6 additions & 6 deletions middleware/src/plugins/manifestLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,12 +810,12 @@ function extractPermissions(
const mcpBlock = permissions?.['mcp'];
const mcpDeclared =
mcpBlock === true || (typeof mcpBlock === 'object' && mcpBlock !== null);
// NOTE: `permissions.devJobs` is no longer parsed — `ctx.devJobs` was deleted
// (see specs/470-dev-platform-plugin/dormant-capabilities.md §2). A stale
// manifest that still declares it stays installable and activatable: unknown
// permission keys are simply ignored here, so the plugin loads unchanged and
// just receives no accessor (it never had a working one). Regression-tested
// in `test/manifestDevJobsLegacyKey.test.ts`.
// NOTE: unknown permission keys are IGNORED here rather than rejected, which
// is what makes a permission removable. When a capability is retired its key
// stops being parsed and its accessor stops being built; a stale manifest
// that still declares the key stays installable and activatable and simply
// receives no accessor. Regression-tested in
// `test/manifestRetiredPermissionKey.test.ts`.
return {
memory_reads: extractStringArray(memory?.['reads']),
memory_writes: extractStringArray(memory?.['writes']),
Expand Down
4 changes: 2 additions & 2 deletions middleware/src/routes/adminEmbeddingProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import {
* one thing it must never do. `installService.reactivate` runs
* `toolPluginRuntime.deactivate`, which calls the KG plugin's `close()`, which
* calls `graphPool.end()` — on the pool the kernel captured ONCE
* (`src/index.ts`) and shares with ~40 subsystems: routines, dev-platform
* webhooks, agent schedules, cost telemetry, MCP audit, `AgentGraphStore`,
* (`src/index.ts`) and shares with ~40 subsystems: routines, plugin webhooks,
* agent schedules, cost telemetry, MCP audit, `AgentGraphStore`,
* `McpConfigService`. After every SUCCESSFUL switch all of them answered
* `Cannot use a pool after calling end on the pool` until the process was
* restarted, i.e. the "switch without restart" feature forced one. The gate is
Expand Down
4 changes: 2 additions & 2 deletions middleware/src/routes/conductorWebhooksInbound.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Issue #437 — inbound Conductor webhooks (`POST /api/hooks/:endpointId`).
*
* MOUNTING CONTRACT (mirrors `devplatform/routes/devWebhooks.ts`): this router MUST be mounted
* MOUNTING CONTRACT (the same one every raw-body webhook router carries): this router MUST be mounted
* BEFORE the global `app.use(express.json(...))`. HMAC verification needs the RAW
* request bytes; once `express.json` has parsed and re-serialised the body, those
* bytes are gone and every signature check fails. The router attaches its OWN
Expand All @@ -16,7 +16,7 @@
* By the time a real request arrives the server has finished booting and the
* accessor always resolves.
*
* ORDER OF OPERATIONS is security-critical, same as devWebhooks:
* ORDER OF OPERATIONS is security-critical:
* 1. Verify the signature FIRST, before trusting anything about the endpoint.
* An unknown endpoint id and a known endpoint with a wrong signature answer
* byte-for-byte the same 401 — the acceptance criterion ("invalid secret
Expand Down
23 changes: 12 additions & 11 deletions middleware/src/services/githubAppJwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import { createSign } from 'node:crypto';
* The shared GitHub App JWT minter.
*
* A GitHub App authenticates to the API as itself with a short-lived RS256 JWT
* signed by its private key, then exchanges that for an installation token. Two
* places mint this JWT: the issue-reporting provider
* (`plugins/builder/githubAppAuth.ts`) and the dev platform's scoped, revocable
* job tokens. Rather than duplicate the signing — a security primitive is the
* last thing to copy-paste — both call this.
* signed by its private key, then exchanges that for an installation token.
* Callers mint it from more than one place — the issue-reporting
* provider (`plugins/builder/githubAppAuth.ts`), and the dev platform plugin's
* scoped, revocable job tokens. Rather than duplicate the signing — a security
* primitive is the last thing to copy-paste — all of them call this.
*
* Lives in `services/` rather than under `devplatform/` where it was first
* written (epic #470 W2): core's builder imported it from there, which made the
* dev-platform tree a dependency of core and blocked extracting that tree into
* its own repository. The primitive itself is generic GitHub App auth and has
* nothing dev-platform-specific about it.
* See `specs/470-dev-platform-plugin/core-decoupling-checklist.md`.
* Lives in `services/` rather than inside the subsystem where it was first
* written (epic #470 W2): core's builder imported it from there, which made a
* to-be-extracted subtree a dependency of core and blocked extracting it into
* its own repository. The primitive itself is generic GitHub App auth, so it
* belongs to core and STAYS here — moving it into the plugin repository would
* recreate the same leak in the opposite direction, across a repo boundary.
* See the epic #470 spec set under `specs/`.
*/

/** GitHub rejects a JWT whose lifetime exceeds 10 minutes; 9 leaves headroom. */
Expand Down
2 changes: 1 addition & 1 deletion middleware/src/services/ssrfGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function toDottedQuad(host: string): string {
}

/** Sync host classification against internal/loopback/metadata ranges. Exported
* so the dev-platform job-policy derivation (epic #470 W1) rejects the same
* so the dev platform plugin's job-policy derivation rejects the same
* internal targets — a clone_url or egress entry pointing at RFC1918 space, the
* cloud-metadata endpoint, or an `.internal`/`localhost` name — with the ONE
* predicate the egress guard uses, so the two can never drift. */
Expand Down
2 changes: 1 addition & 1 deletion middleware/test/adminEmbeddingProvider.harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Shared harness for the `/api/v1/admin/embedding-provider` route tests.
*
* Extracted so the spec file stays about behaviour. Same convention as
* `devplatform/devPlatformRoutes.harness.ts` — the `.harness.ts` suffix keeps
* the other route harnesses in this tree — the `.harness.ts` suffix keeps
* it out of the test-file glob the runner uses.
*
* It models the runtime the way it actually behaves: `activate(id)` publishes
Expand Down
4 changes: 2 additions & 2 deletions middleware/test/adversarial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,8 @@ deterministic tier additionally runs on every PR *for free* as part of

- **`direct_injection` and `indirect_injection` currently have NO Tier A probe.**
Both vectors were measured by `brief_delimiter`, which ran the real
`composeBrief` out of `src/devplatform/`. Epic #470 C10 moved the Dev Platform
to `byte5ai/omadia-dev-platform`, and a probe against a module core no longer
`composeBrief` out of the dev platform tree. Epic #470 C10 moved that tree to
its own repository, and a probe against a module core no longer
ships measures a library rather than a deployed defense — so the probe, its
five corpus scenarios and their baseline rows left with it. **This is a real
coverage reduction, recorded rather than absorbed silently.** Two ways to close
Expand Down
6 changes: 3 additions & 3 deletions middleware/test/conductorAwaitStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ describe('ConductorAwaitStore.listWaiting — the operator inbox', () => {
awaitRow('a2', 'telegram'),
awaitRow('a3', 'web'),
// The literal this query used to exclude. Without a row carrying it, the
// test cannot detect `AND channel_type <> 'dev_job'` being restored — it
// test cannot detect a `channel_type` exclusion filter being restored — it
// would stay green against the exact regression it exists to catch.
awaitRow('a4', 'dev_job'),
awaitRow('a4', 'machine'),
]);
const store = new ConductorAwaitStore(pool as never);

Expand All @@ -93,7 +93,7 @@ describe('ConductorAwaitStore.listWaiting — the operator inbox', () => {
assert.equal(inbox.length, 4);
assert.deepEqual(
inbox.map((a) => a.channelType).sort(),
['dev_job', 'teams', 'telegram', 'web'],
['machine', 'teams', 'telegram', 'web'],
);
});

Expand Down
2 changes: 1 addition & 1 deletion middleware/test/embeddingModelGate.pg.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
* leaves the corpus in the state the resume path expects.
*
* Self-skips when no Postgres is reachable, same convention as
* test/devplatform/*.pg.test.ts — never fails the suite on a machine without
* every other *.pg.test.ts — never fails the suite on a machine without
* a database.
*/

Expand Down
Loading
Loading