Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 9 additions & 2 deletions middleware/migrations/0024_dev_platform_w3.sql
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,15 @@ ALTER TABLE dev_repos
ADD COLUMN IF NOT EXISTS policy_overrides jsonb NOT NULL DEFAULT '{}'::jsonb;

-- --- link a parked job to its holding conductor await (W3 §5) ---------------
-- Nullable: only Conductor-driven jobs park on an await. One-await invariant is
-- enforced in conductorBridge, not by a DB constraint here.
-- ORPHANED COLUMN — do NOT read this as evidence of a live feature.
-- It was added for the Conductor `dev.job` step, which was built but never wired:
-- the run executor was always constructed without the port, so the dispatch branch
-- was permanently false and NOTHING EVER WROTE THIS COLUMN. That step was deleted
-- in epic #470 C5 (see the dormant-capabilities spec, §1). The column stays only
-- because migrations are forward-only here and a DROP is the one irreversible act
-- in that change. It has no reader and no writer in `src/`, and it is always NULL.
-- If the step is ever rebuilt (as a new feature, in the plugin repo) it may reclaim
-- this column; otherwise a future migration squash may drop it.
ALTER TABLE dev_jobs
ADD COLUMN IF NOT EXISTS conductor_await_id text;

Expand Down
32 changes: 10 additions & 22 deletions middleware/src/conductor/awaitStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,38 +114,26 @@ export class ConductorAwaitStore {
}

/**
* All waiting HUMAN awaits (the operator inbox). Dev-job awaits (`channel_type='dev_job'`,
* Epic #470 W3) are excluded: they have no human holder and are resolved by a terminal dev-job
* outcome, not an operator response — surfacing them would show a phantom, un-actionable row
* whose `respond` can only ever be rejected by the authz gate.
* All waiting awaits — the operator inbox.
*
* 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
* remember to add itself to survive it. If such a kind ever lands, it names itself here.
*/
async listWaiting(limit = 100): Promise<ConductorAwait[]> {
const r = await this.pool.query<AwaitRow>(
`SELECT ${COLS} FROM conductor_awaits
WHERE status = 'waiting' AND channel_type <> 'dev_job'
WHERE status = 'waiting'
ORDER BY created_at ASC LIMIT $1`,
[Math.min(Math.max(1, limit), 500)],
);
return r.rows.map(toAwait);
}

/**
* Waiting dev-job awaits (`channel_type='dev_job'`) — the reconciliation sweep's input
* (Epic #470 W3). Deliberately the COMPLEMENT of {@link listWaiting}: these carry a synthetic
* `dev_job:<jobId>` principal and are recovered by `reconcileTerminalDevJobAwaits`, which asks
* the dev-job port whether the bound job is already terminal (closing the terminal-before-bind
* lost-wakeup window).
*/
async listWaitingDevJobAwaits(limit = 200): Promise<ConductorAwait[]> {
const r = await this.pool.query<AwaitRow>(
`SELECT ${COLS} FROM conductor_awaits
WHERE status = 'waiting' AND channel_type = 'dev_job'
ORDER BY created_at ASC LIMIT $1`,
[Math.min(Math.max(1, limit), 1000)],
);
return r.rows.map(toAwait);
}

/** Waiting awaits whose deadline has passed (for the deadline worker). */
async listDue(now: Date): Promise<ConductorAwait[]> {
const r = await this.pool.query<AwaitRow>(
Expand Down
122 changes: 0 additions & 122 deletions middleware/src/conductor/devJobStepEffect.ts

This file was deleted.

4 changes: 2 additions & 2 deletions middleware/src/conductor/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,8 @@ export function createConductorRouter(deps: ConductorRouterDeps): Router {
if (err instanceof AwaitNotPendingError) {
res.status(409).json({ code: 'conductor.await_not_pending', message: err.message });
} else if (err instanceof AwaitResponderNotHolderError) {
// A non-holder tried to answer (incl. the phantom `dev_job:<id>` principal an operator
// must never own). The authz gate already refused; surface it as 403, not a generic 500.
// A non-holder tried to answer. The authz gate already refused; surface it as 403,
// not a generic 500.
res.status(403).json({ code: 'conductor.await_forbidden', message: err.message });
} else {
console.error('[conductor] respond failed:', err);
Expand Down
Loading
Loading