C4 follow-up #3: RFC 8594 Deprecation/Sunset headers + call counter on legacy hire routes

This commit is contained in:
ZacharyZhang-NY committed 2026-04-20 14:56:34 -04:00
1 parent 1fad88bad1
commit d2b6f91023
4 files changed
+104

No files matched your search

@@ -0,0 +1,36 @@
/**
* In-process counter for deprecated HTTP routes (C4-followup #3).
*
* The counters let us assert "legacy HTTP hire traffic has hit zero"
* before physically deleting the deprecated handlers. Keys are the
* route label the handler passes in (e.g. `POST /api/companies/:companyId/agents`).
*
* Scope is intentionally narrow: module-level Map, synchronous mutation,
* no IO, no fallback. An empty label is a programmer error — we throw
* so it surfaces in tests instead of silently recording under "".
*
* The counter is not exposed over HTTP yet (scope creep). Internal
* code reads via `readDeprecationCounters()`.
*/
const deprecationCallCounters = new Map<string, number>();
export function incrementDeprecationCall(routeLabel: string): void {
if (typeof routeLabel !== "string" || routeLabel.length === 0) {
throw new Error("incrementDeprecationCall requires a non-empty routeLabel");
}
const previous = deprecationCallCounters.get(routeLabel) ?? 0;
deprecationCallCounters.set(routeLabel, previous + 1);
}
export function readDeprecationCounters(): Record<string, number> {
const snapshot: Record<string, number> = {};
for (const [label, count] of deprecationCallCounters) {
snapshot[label] = count;
}
return snapshot;
}
export function resetDeprecationCounters(): void {
deprecationCallCounters.clear();
}
+13
View File
@@ -10,8 +10,15 @@ import {
} from "../agent-permissions";
import { resolveApprovalDecision } from "../approval-policy";
import { shouldAutoApprove } from "../execution-policy";
import { incrementDeprecationCall } from "./deprecation-counter";
import { requireParam, type AgentApiServer } from "./server-core";
// C4-followup #3: RFC 8594 deprecation headers for the legacy HTTP hire
// route. Sunset is the 30-day removal gate — if the counter stays zero
// through this date, delete the handler.
const LEGACY_HIRE_ROUTE_LABEL = "POST /api/companies/:companyId/agents";
const LEGACY_HIRE_SUNSET = new Date("2026-05-20T00:00:00Z").toUTCString();
export function registerAgentRoutes(server: AgentApiServer) {
const db = server.db;
const callbacks = server.callbacks;
@@ -106,6 +113,12 @@ export function registerAgentRoutes(server: AgentApiServer) {
* so residual callers are easy to audit.
*/
server.addRoute("POST", "/api/companies/:companyId/agents", (_req, res, params, token, body) => {
// RFC 8594 deprecation signaling — emit on every response from this
// handler regardless of downstream validation outcome.
res.setHeader("Deprecation", "true");
res.setHeader("Sunset", LEGACY_HIRE_SUNSET);
res.setHeader("Link", '</docs/proposals/C4-followup.md>; rel="deprecation"');
incrementDeprecationCall(LEGACY_HIRE_ROUTE_LABEL);
const companyId = requireParam(params, "companyId");
if (companyId !== token.companyId) {
server.json(res, 403, { error: "Company mismatch" });
+12
View File
@@ -3,8 +3,16 @@ import log from "electron-log/main.js";
import { approvalInputSchema } from "@shared/contracts";
import { resolveApprovalDecision } from "../approval-policy";
import { shouldAutoApprove } from "../execution-policy";
import { incrementDeprecationCall } from "./deprecation-counter";
import { requireParam, type AgentApiServer } from "./server-core";
// C4-followup #3: RFC 8594 deprecation headers for the legacy
// approve_ceo_strategy HTTP path. Only this approval type is
// deprecated — other types still use this route as first-class.
const LEGACY_APPROVE_CEO_STRATEGY_LABEL =
"POST /api/companies/:companyId/approvals type=approve_ceo_strategy";
const LEGACY_APPROVE_CEO_STRATEGY_SUNSET = new Date("2026-05-20T00:00:00Z").toUTCString();
const approvalListQuerySchema = z.object({
requestedByAgentId: z.string().uuid().optional(),
relatedTaskId: z.string().uuid().optional(),
@@ -43,6 +51,10 @@ export function registerApprovalRoutes(server: AgentApiServer) {
}
const input = parsed.data;
if (input.type === "approve_ceo_strategy") {
res.setHeader("Deprecation", "true");
res.setHeader("Sunset", LEGACY_APPROVE_CEO_STRATEGY_SUNSET);
res.setHeader("Link", '</docs/proposals/C4-followup.md>; rel="deprecation"');
incrementDeprecationCall(LEGACY_APPROVE_CEO_STRATEGY_LABEL);
log.warn(
`[deprecated][C4] POST /api/companies/${companyId}/approvals type=approve_ceo_strategy — prefer the file_approval internal tool (caller agent=${token.agentId}).`,
);