(
_hookName: string,
{app}: ArgsExpressType,
cb: Function,
)
| 139 | const tierAllowsActions = (): boolean => TIER2_TIERS.has(settings.updates.tier); |
| 140 | |
| 141 | export const expressCreateServer = ( |
| 142 | _hookName: string, |
| 143 | {app}: ArgsExpressType, |
| 144 | cb: Function, |
| 145 | ): void => { |
| 146 | // Always register the routes; gate at request time so a runtime tier change |
| 147 | // takes effect on the next request rather than requiring a restart. |
| 148 | // The early 404 below preserves Qodo #1's "disabled path matches prior |
| 149 | // behaviour (no Tier 2 endpoints existed before this PR)" requirement. |
| 150 | const tierGate = (req: any, res: any, next: Function) => { |
| 151 | if (!tierAllowsActions()) return res.status(404).send('Not found'); |
| 152 | next(); |
| 153 | }; |
| 154 | app.use(['/admin/update/apply', '/admin/update/cancel', '/admin/update/acknowledge', '/admin/update/log'], tierGate); |
| 155 | |
| 156 | app.post('/admin/update/apply', wrapAsync(async (req: any, res: any) => { |
| 157 | if (!requireAdmin(req, res)) return; |
| 158 | |
| 159 | // HTTP-specific pre-checks: produce structured 4xx error codes the UI |
| 160 | // localises. The pipeline duplicates these as safety, but mapping cleanly |
| 161 | // to status codes is easier when we know the failure kind up-front. |
| 162 | const state = await loadState(stateFilePath()); |
| 163 | if (!state.latest) return res.status(409).json({error: 'no-known-latest'}); |
| 164 | if (!isValidTag(state.latest.tag)) { |
| 165 | return res.status(409).json({error: 'invalid-tag-in-state'}); |
| 166 | } |
| 167 | // Allowed entry statuses: idle / verified / preflight-failed / rolled-back / scheduled. |
| 168 | // `scheduled` lets the admin "Apply now" during the Tier 3 grace window. |
| 169 | const allowedEntry = ['idle', 'verified', 'preflight-failed', 'rolled-back', 'scheduled']; |
| 170 | if (!allowedEntry.includes(state.execution.status)) { |
| 171 | return res.status(409).json({error: `execution-busy:${state.execution.status}`}); |
| 172 | } |
| 173 | const installMethod = getDetectedInstallMethod(); |
| 174 | const policy = evaluatePolicy({ |
| 175 | installMethod, |
| 176 | tier: settings.updates.tier, |
| 177 | current: getEpVersion(), |
| 178 | latest: state.latest.version, |
| 179 | executionStatus: state.execution.status, |
| 180 | }); |
| 181 | if (!policy.canManual) { |
| 182 | return res.status(409).json({error: 'policy-denied', reason: policy.reason}); |
| 183 | } |
| 184 | |
| 185 | // Admin clicked "Apply now" during the Tier 3 grace window. Drop the |
| 186 | // pending scheduler timer so it doesn't later fire and attempt a second |
| 187 | // apply (Qodo #3). The pipeline will overwrite execution → preflight |
| 188 | // momentarily, but cancelling the timer is what guarantees we don't race. |
| 189 | if (state.execution.status === 'scheduled') cancelScheduler(); |
| 190 | |
| 191 | const targetTag = state.latest.tag; |
| 192 | let responded = false; |
| 193 | |
| 194 | try { |
| 195 | const result = await applyUpdate({ |
| 196 | targetTag, |
| 197 | deps: { |
| 198 | loadState: () => loadState(stateFilePath()), |
nothing calls this directly
no test coverage detected