hub-proposal-create-augment.mjs
sha256:e4c529f14a0bb908c1caaaeb3f95f3623a1a82e636e7e3722ca2cd3dc9821263
security: npm audit fix pre-bridge 2026-07-29
Human
41 days ago
| 1 | /** |
| 2 | * Merge org evaluation policy + deterministic review triggers into a proposal create payload. |
| 3 | * Used by self-hosted Hub and hosted gateway (POST /api/v1/proposals body). |
| 4 | * |
| 5 | * HOSTED-WRITE-EVAL E1: after triggers, Scooling personal self-apply class gets |
| 6 | * evaluation_status=passed (never self-pass elevated / auto-flagged). |
| 7 | * |
| 8 | * SEC-KN-2 / Pass 2 P2: client-supplied evaluation_status / evaluated_by / evaluated_at |
| 9 | * are stripped from every create body before policy/triggers/E1. Only server-side |
| 10 | * evaluation (policy pending, E1 self-pass, or the evaluate endpoint) may set them. |
| 11 | */ |
| 12 | |
| 13 | import { getProposalEvaluationRequired } from './hub-proposal-policy.mjs'; |
| 14 | import { loadReviewTriggers, applyReviewTriggers } from './hub-proposal-review-triggers.mjs'; |
| 15 | import { applyPersonalSelfApplyEvaluationE1 } from './hub-proposal-personal-self-apply.mjs'; |
| 16 | |
| 17 | /** Client-forgeable evaluation audit fields — never trusted on create. */ |
| 18 | export const CLIENT_EVALUATION_CREATE_FIELDS = Object.freeze([ |
| 19 | 'evaluation_status', |
| 20 | 'evaluated_by', |
| 21 | 'evaluated_at', |
| 22 | ]); |
| 23 | |
| 24 | /** |
| 25 | * Remove client-supplied evaluation fields from a proposal-create body. |
| 26 | * Mutates a shallow copy; does not mutate the input object. |
| 27 | * |
| 28 | * @param {Record<string, unknown>} body |
| 29 | * @returns {Record<string, unknown>} |
| 30 | */ |
| 31 | export function stripClientEvaluationFields(body) { |
| 32 | if (!body || typeof body !== 'object' || Buffer.isBuffer(body)) return body; |
| 33 | const next = { ...body }; |
| 34 | for (const key of CLIENT_EVALUATION_CREATE_FIELDS) { |
| 35 | delete next[key]; |
| 36 | } |
| 37 | return next; |
| 38 | } |
| 39 | |
| 40 | /** |
| 41 | * @param {Record<string, unknown>} body - parsed JSON body (mutated copy returned) |
| 42 | * @param {string} dataDir |
| 43 | * @param {{ |
| 44 | * evaluationRequired?: boolean, |
| 45 | * evaluatedBy?: string, |
| 46 | * evaluatedAt?: string, |
| 47 | * sessionBound?: boolean, |
| 48 | * authorActorId?: string|null, |
| 49 | * }} [policyOptions] - when `evaluationRequired` is boolean, skip file/env read |
| 50 | * @returns {Record<string, unknown>} |
| 51 | */ |
| 52 | export function augmentProposalCreateRequestBody(body, dataDir, policyOptions = {}) { |
| 53 | if (!body || typeof body !== 'object' || Buffer.isBuffer(body)) return body; |
| 54 | const policyPending = |
| 55 | typeof policyOptions.evaluationRequired === 'boolean' |
| 56 | ? policyOptions.evaluationRequired |
| 57 | : getProposalEvaluationRequired(dataDir); |
| 58 | const triggers = loadReviewTriggers(dataDir); |
| 59 | const labels = Array.isArray(body.labels) ? body.labels : []; |
| 60 | const applied = applyReviewTriggers(triggers, { |
| 61 | path: String(body.path ?? ''), |
| 62 | body: String(body.body ?? ''), |
| 63 | intent: String(body.intent ?? ''), |
| 64 | labels, |
| 65 | }); |
| 66 | // SEC-KN-2: discard client evaluation fields before any server assignment. |
| 67 | let next = stripClientEvaluationFields(body); |
| 68 | const needPending = policyPending || applied.forcePending; |
| 69 | if (needPending) { |
| 70 | next.evaluation_status = 'pending'; |
| 71 | } |
| 72 | if (applied.review_queue) next.review_queue = applied.review_queue; |
| 73 | if (applied.review_severity) next.review_severity = applied.review_severity; |
| 74 | if (applied.auto_flag_reasons.length) { |
| 75 | next.auto_flag_reasons_json = JSON.stringify(applied.auto_flag_reasons); |
| 76 | next.auto_flag_reasons = applied.auto_flag_reasons; |
| 77 | } |
| 78 | // E1 AFTER trigger merge — elevated/auto-flag still fail P6 and stay pending. |
| 79 | next = applyPersonalSelfApplyEvaluationE1(next, { |
| 80 | evaluatedBy: policyOptions.evaluatedBy, |
| 81 | evaluatedAt: policyOptions.evaluatedAt, |
| 82 | sessionBound: policyOptions.sessionBound, |
| 83 | authorActorId: policyOptions.authorActorId ?? policyOptions.evaluatedBy, |
| 84 | }); |
| 85 | return next; |
| 86 | } |
File History
6 commits
sha256:e4c529f14a0bb908c1caaaeb3f95f3623a1a82e636e7e3722ca2cd3dc9821263
security: npm audit fix pre-bridge 2026-07-29
Human
41 days ago
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf
mirror: GitHub Phase A durable MCP OAuth (#270)
Human
minor
⚠
57 days ago
sha256:873e30b7fafe601346295f8f4289f388f21d8f715f28584d5481899ba2b714fc
Merge pull request #249 from aaronrene/muse-mirror
Agent
75 days ago
sha256:d8c648b20a4d53b2673c5c082ee7edfa7b2fc9b11080832da1f38807b6bf940b
fix(7C-L1b): route hosted delegation proposals through cani…
Human
minor
⚠
77 days ago
sha256:2827ba9e7632a4b141c50caf1e8f7d77abbc3515be20e7465f2bccb0ac4edf91
fix: repair endpoint now sets has_active_subscription when …
Human
minor
⚠
97 days ago
sha256:6a102aafafdfe7e70a24f4e59740200f0ee713ce7915f1b53e9d4ba5ee8b4410
Initial Muse snapshot
Human
129 days ago