diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json
index 22720d2bc..c1bb49faf 100644
--- a/apps/web/messages/en.json
+++ b/apps/web/messages/en.json
@@ -7132,6 +7132,34 @@
"body": "六項預檢與人工接受目前都還是 0。"
}
},
+ "metadataFieldsTitle": "待補欄位封套",
+ "metadataFieldsState": "已填",
+ "metadataFields": {
+ "ownerRoleTeam": {
+ "title": "負責人角色 / 團隊",
+ "body": "只收角色或團隊名稱,不收帳密或個人私密資料。"
+ },
+ "decision": {
+ "title": "判定",
+ "body": "必須落在各路線允許的判定值,不等於批准執行。"
+ },
+ "decisionReason": {
+ "title": "判定理由",
+ "body": "只收脫敏理由摘要,不保存原始回覆全文。"
+ },
+ "affectedScope": {
+ "title": "受影響範圍",
+ "body": "標出 repo、namespace 或來源範圍,不觸發寫入。"
+ },
+ "redactedEvidenceRefs": {
+ "title": "脫敏證據參照",
+ "body": "只能指向文件、snapshot 或已脫敏中繼資料。"
+ },
+ "followupOwner": {
+ "title": "後續負責人",
+ "body": "只標示後續追蹤對象,不能開執行閘門。"
+ }
+ },
"nextGates": {
"redactedOwnerResponse": {
"title": "收到脫敏回覆",
diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json
index 22720d2bc..c1bb49faf 100644
--- a/apps/web/messages/zh-TW.json
+++ b/apps/web/messages/zh-TW.json
@@ -7132,6 +7132,34 @@
"body": "六項預檢與人工接受目前都還是 0。"
}
},
+ "metadataFieldsTitle": "待補欄位封套",
+ "metadataFieldsState": "已填",
+ "metadataFields": {
+ "ownerRoleTeam": {
+ "title": "負責人角色 / 團隊",
+ "body": "只收角色或團隊名稱,不收帳密或個人私密資料。"
+ },
+ "decision": {
+ "title": "判定",
+ "body": "必須落在各路線允許的判定值,不等於批准執行。"
+ },
+ "decisionReason": {
+ "title": "判定理由",
+ "body": "只收脫敏理由摘要,不保存原始回覆全文。"
+ },
+ "affectedScope": {
+ "title": "受影響範圍",
+ "body": "標出 repo、namespace 或來源範圍,不觸發寫入。"
+ },
+ "redactedEvidenceRefs": {
+ "title": "脫敏證據參照",
+ "body": "只能指向文件、snapshot 或已脫敏中繼資料。"
+ },
+ "followupOwner": {
+ "title": "後續負責人",
+ "body": "只標示後續追蹤對象,不能開執行閘門。"
+ }
+ },
"nextGates": {
"redactedOwnerResponse": {
"title": "收到脫敏回覆",
diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx
index fa7ff2252..a8a6d8458 100644
--- a/apps/web/src/app/[locale]/iwooos/page.tsx
+++ b/apps/web/src/app/[locale]/iwooos/page.tsx
@@ -105,6 +105,14 @@ type S49OwnerResponseIntakeDeliveryItem = {
tone: 'steady' | 'warn' | 'locked'
}
+type S49OwnerResponseMetadataIntakeField = {
+ key: string
+ code: string
+ state: string
+ icon: typeof ShieldCheck
+ tone: 'steady' | 'warn' | 'locked'
+}
+
type FastProgressItem = {
key: string
value: string
@@ -1143,6 +1151,15 @@ const s49OwnerResponseIntakeDeliveryItems: S49OwnerResponseIntakeDeliveryItem[]
{ key: 'reviewTrail', value: '0', icon: SearchCheck, tone: 'locked' },
]
+const s49OwnerResponseMetadataIntakeFields: S49OwnerResponseMetadataIntakeField[] = [
+ { key: 'ownerRoleTeam', code: 'F1', state: '0', icon: Bell, tone: 'warn' },
+ { key: 'decision', code: 'F2', state: '0', icon: ClipboardCheck, tone: 'warn' },
+ { key: 'decisionReason', code: 'F3', state: '0', icon: FileText, tone: 'warn' },
+ { key: 'affectedScope', code: 'F4', state: '0', icon: Radar, tone: 'warn' },
+ { key: 'redactedEvidenceRefs', code: 'F5', state: '0', icon: SearchCheck, tone: 'locked' },
+ { key: 'followupOwner', code: 'F6', state: '0', icon: ShieldCheck, tone: 'locked' },
+]
+
const s49OwnerResponseIntakeBoundaries = [
's4_9_owner_response_intake_frontstage_lane_count=5',
's4_9_owner_response_intake_next_gate_count=3',
@@ -1167,6 +1184,16 @@ const s49OwnerResponseIntakeBoundaries = [
's4_9_owner_response_intake_handoff_queue_runtime_gate_count=0',
's4_9_owner_response_intake_handoff_queue_raw_payload_allowed=false',
's4_9_owner_response_intake_handoff_queue_action_buttons_allowed=false',
+ 's4_9_owner_response_metadata_intake_field_count=6',
+ 's4_9_owner_response_metadata_intake_required_count=6',
+ 's4_9_owner_response_metadata_intake_filled_count=0',
+ 's4_9_owner_response_metadata_intake_received_count=0',
+ 's4_9_owner_response_metadata_intake_accepted_count=0',
+ 's4_9_owner_response_metadata_intake_runtime_gate_count=0',
+ 's4_9_owner_response_metadata_intake_redacted_ref_required=true',
+ 's4_9_owner_response_metadata_intake_raw_payload_allowed=false',
+ 's4_9_owner_response_metadata_intake_secret_plaintext_allowed=false',
+ 's4_9_owner_response_metadata_intake_action_buttons_allowed=false',
's4_9_owner_response_intake_runtime_gate_opened=false',
's4_9_owner_response_intake_action_buttons_allowed=false',
'runtime_execution_authorized=false',
@@ -11772,6 +11799,52 @@ function IwoooSS49OwnerResponseIntakeBoard() {
)
})}
+
+
+ {t('metadataFieldsTitle')}
+
+
+ {s49OwnerResponseMetadataIntakeFields.map(item => {
+ const Icon = item.icon
+ return (
+
+
+
+ {item.code}
+
+
+ {t(`metadataFields.${item.key}.title` as never)}
+
+
+ {t(`metadataFields.${item.key}.body` as never)}
+
+
+ {t('metadataFieldsState')}: {item.state}
+
+
+ )
+ })}
+
+
None:
"iwooos_projection.summary.s4_9_owner_response_intake_handoff_queue_action_buttons_allowed",
iwooos_projection["summary"]["s4_9_owner_response_intake_handoff_queue_action_buttons_allowed"],
)
+ assert_equal(
+ "iwooos_projection.summary.s4_9_owner_response_metadata_intake_field_count",
+ iwooos_projection["summary"]["s4_9_owner_response_metadata_intake_field_count"],
+ 6,
+ )
+ assert_equal(
+ "iwooos_projection.summary.s4_9_owner_response_metadata_intake_required_count",
+ iwooos_projection["summary"]["s4_9_owner_response_metadata_intake_required_count"],
+ 6,
+ )
+ for count_key in [
+ "s4_9_owner_response_metadata_intake_filled_count",
+ "s4_9_owner_response_metadata_intake_received_count",
+ "s4_9_owner_response_metadata_intake_accepted_count",
+ "s4_9_owner_response_metadata_intake_runtime_gate_count",
+ ]:
+ assert_equal(
+ f"iwooos_projection.summary.{count_key}",
+ iwooos_projection["summary"][count_key],
+ 0,
+ )
+ assert_true(
+ "iwooos_projection.summary.s4_9_owner_response_metadata_intake_redacted_ref_required",
+ iwooos_projection["summary"]["s4_9_owner_response_metadata_intake_redacted_ref_required"],
+ )
+ for false_key in [
+ "s4_9_owner_response_metadata_intake_raw_payload_allowed",
+ "s4_9_owner_response_metadata_intake_secret_plaintext_allowed",
+ "s4_9_owner_response_metadata_intake_action_buttons_allowed",
+ ]:
+ assert_false(
+ f"iwooos_projection.summary.{false_key}",
+ iwooos_projection["summary"][false_key],
+ )
assert_equal(
"iwooos_projection.summary.all_product_coverage_snapshot_default_summary_mode",
iwooos_projection["summary"]["all_product_coverage_snapshot_default_summary_mode"],
@@ -14175,6 +14209,84 @@ def validate(root: Path) -> None:
f"iwooos_projection.s4_9_owner_response_intake_delivery_cards.{item['card_id']}.not_authorization",
item["not_authorization"],
)
+ expected_s4_9_metadata_intake_field_ids = [
+ "ownerRoleTeam",
+ "decision",
+ "decisionReason",
+ "affectedScope",
+ "redactedEvidenceRefs",
+ "followupOwner",
+ ]
+ expected_s4_9_metadata_intake_source_fields = [
+ "owner_role_or_team",
+ "decision",
+ "decision_reason",
+ "affected_scope",
+ "evidence_refs",
+ "followup_owner",
+ ]
+ s4_9_owner_response_metadata_intake_fields = iwooos_projection[
+ "s4_9_owner_response_metadata_intake_fields"
+ ]
+ assert_equal(
+ "iwooos_projection.s4_9_owner_response_metadata_intake_fields.ids",
+ [item["field_id"] for item in s4_9_owner_response_metadata_intake_fields],
+ expected_s4_9_metadata_intake_field_ids,
+ )
+ assert_equal(
+ "iwooos_projection.s4_9_owner_response_metadata_intake_fields.display_order",
+ [item["display_order"] for item in s4_9_owner_response_metadata_intake_fields],
+ list(range(1, len(expected_s4_9_metadata_intake_field_ids) + 1)),
+ )
+ assert_equal(
+ "iwooos_projection.s4_9_owner_response_metadata_intake_fields.source_required_fields",
+ [item["source_required_field"] for item in s4_9_owner_response_metadata_intake_fields],
+ expected_s4_9_metadata_intake_source_fields,
+ )
+ for item in s4_9_owner_response_metadata_intake_fields:
+ assert_equal(
+ f"iwooos_projection.s4_9_owner_response_metadata_intake_fields.{item['field_id']}.display_mode",
+ item["display_mode"],
+ "first_screen_s4_9_owner_response_metadata_intake_field",
+ )
+ assert_equal(
+ f"iwooos_projection.s4_9_owner_response_metadata_intake_fields.{item['field_id']}.field_status",
+ item["field_status"],
+ "waiting_owner_response_metadata",
+ )
+ assert_true(
+ f"iwooos_projection.s4_9_owner_response_metadata_intake_fields.{item['field_id']}.required",
+ item["required"],
+ )
+ for count_key in [
+ "filled_count",
+ "received_response_count",
+ "accepted_response_count",
+ "runtime_gate_count",
+ ]:
+ assert_equal(
+ f"iwooos_projection.s4_9_owner_response_metadata_intake_fields.{item['field_id']}.{count_key}",
+ item[count_key],
+ 0,
+ )
+ assert_true(
+ f"iwooos_projection.s4_9_owner_response_metadata_intake_fields.{item['field_id']}.redacted_reference_required",
+ item["redacted_reference_required"],
+ )
+ for false_key in [
+ "raw_payload_allowed",
+ "secret_plaintext_allowed",
+ "runtime_execution_authorized",
+ "action_buttons_allowed",
+ ]:
+ assert_false(
+ f"iwooos_projection.s4_9_owner_response_metadata_intake_fields.{item['field_id']}.{false_key}",
+ item[false_key],
+ )
+ assert_true(
+ f"iwooos_projection.s4_9_owner_response_metadata_intake_fields.{item['field_id']}.not_authorization",
+ item["not_authorization"],
+ )
for text in [
'data-testid="iwooos-first-screen-depth-map-board"',
'data-testid="iwooos-first-screen-depth-map-layers"',
@@ -14422,6 +14534,8 @@ def validate(root: Path) -> None:
'data-testid="iwooos-s49-owner-response-intake-completion-rail"',
'data-testid="iwooos-s49-owner-response-intake-delivery-cards"',
"iwooos-s49-owner-response-intake-delivery-card-${item.key}",
+ 'data-testid="iwooos-s49-owner-response-metadata-intake-fields"',
+ "iwooos-s49-owner-response-metadata-intake-field-${item.key}",
'data-testid="iwooos-s49-owner-response-intake-next-gates"',
"iwooos-s49-owner-response-intake-next-gate-${item.key}",
'data-testid="iwooos-s49-owner-response-intake-lanes"',
@@ -14431,10 +14545,13 @@ def validate(root: Path) -> None:
"s49OwnerResponseIntakeNextGates",
"s49OwnerResponseIntakeBlockerFocus",
"s49OwnerResponseIntakeDeliveryItems",
+ "s49OwnerResponseMetadataIntakeFields",
"s49OwnerResponseIntakeBoundaries",
"s49OwnerResponseIntake",
"blockerFocus",
"deliveryItems",
+ "metadataFields",
+ "metadataFieldsTitle",
"ownerDecision",
"redactedEvidence",
"reviewTrail",
@@ -14443,9 +14560,20 @@ def validate(root: Path) -> None:
"redactedOwnerResponse",
"preflightChecks",
"humanAcceptance",
+ "ownerRoleTeam",
+ "decisionReason",
+ "affectedScope",
+ "redactedEvidenceRefs",
+ "followupOwner",
"G1",
"G2",
"G3",
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
"received",
"preflight",
"acceptance",
@@ -14521,6 +14649,16 @@ def validate(root: Path) -> None:
"s4_9_owner_response_intake_handoff_queue_runtime_gate_count=0",
"s4_9_owner_response_intake_handoff_queue_raw_payload_allowed=false",
"s4_9_owner_response_intake_handoff_queue_action_buttons_allowed=false",
+ "s4_9_owner_response_metadata_intake_field_count=6",
+ "s4_9_owner_response_metadata_intake_required_count=6",
+ "s4_9_owner_response_metadata_intake_filled_count=0",
+ "s4_9_owner_response_metadata_intake_received_count=0",
+ "s4_9_owner_response_metadata_intake_accepted_count=0",
+ "s4_9_owner_response_metadata_intake_runtime_gate_count=0",
+ "s4_9_owner_response_metadata_intake_redacted_ref_required=true",
+ "s4_9_owner_response_metadata_intake_raw_payload_allowed=false",
+ "s4_9_owner_response_metadata_intake_secret_plaintext_allowed=false",
+ "s4_9_owner_response_metadata_intake_action_buttons_allowed=false",
"s4_9_owner_response_intake_runtime_gate_opened=false",
"s4_9_owner_response_intake_action_buttons_allowed=false",
"runtime_execution_authorized=false",
diff --git a/scripts/security/source-control-owner-response-guard.py b/scripts/security/source-control-owner-response-guard.py
index d954b3a7d..acff9e764 100755
--- a/scripts/security/source-control-owner-response-guard.py
+++ b/scripts/security/source-control-owner-response-guard.py
@@ -101,6 +101,22 @@ LANES = [
"response-legacy-or-inaccessible-disposition",
],
"expected_handoff_queue_required_field_counts": [6, 6, 6, 8, 7],
+ "expected_metadata_intake_field_ids": [
+ "ownerRoleTeam",
+ "decision",
+ "decisionReason",
+ "affectedScope",
+ "redactedEvidenceRefs",
+ "followupOwner",
+ ],
+ "expected_metadata_intake_source_fields": [
+ "owner_role_or_team",
+ "decision",
+ "decision_reason",
+ "affected_scope",
+ "evidence_refs",
+ "followup_owner",
+ ],
},
{
"lane_id": "s4_10_github_target_owner_decision_response",
@@ -846,6 +862,93 @@ def validate(root: Path) -> None:
f"{lane['lane_id']}.{item['handoff_lane_id']}.still_forbidden.{forbidden_action}",
forbidden_action in item["still_forbidden"],
)
+ expected_metadata_intake_field_ids = lane.get("expected_metadata_intake_field_ids")
+ if expected_metadata_intake_field_ids is not None:
+ metadata_envelope = snapshot["owner_response_metadata_intake_envelope"]
+ expected_metadata_intake_source_fields = lane["expected_metadata_intake_source_fields"]
+ assert_equal(
+ f"{lane['lane_id']}.owner_response_metadata_intake_field_count",
+ summary["owner_response_metadata_intake_field_count"],
+ len(expected_metadata_intake_field_ids),
+ )
+ assert_equal(
+ f"{lane['lane_id']}.owner_response_metadata_intake_required_count",
+ summary["owner_response_metadata_intake_required_count"],
+ len(expected_metadata_intake_field_ids),
+ )
+ for count_key in [
+ "owner_response_metadata_intake_filled_count",
+ "owner_response_metadata_intake_received_count",
+ "owner_response_metadata_intake_accepted_count",
+ "owner_response_metadata_intake_runtime_gate_count",
+ ]:
+ assert_equal(f"{lane['lane_id']}.{count_key}", summary[count_key], 0)
+ assert_true(
+ f"{lane['lane_id']}.owner_response_metadata_intake_redacted_ref_required",
+ summary["owner_response_metadata_intake_redacted_ref_required"],
+ )
+ for false_key in [
+ "owner_response_metadata_intake_raw_payload_allowed",
+ "owner_response_metadata_intake_secret_plaintext_allowed",
+ "owner_response_metadata_intake_action_buttons_allowed",
+ ]:
+ assert_false(f"{lane['lane_id']}.{false_key}", summary[false_key])
+ assert_equal(
+ f"{lane['lane_id']}.owner_response_metadata_intake_field_ids",
+ [item["field_id"] for item in metadata_envelope],
+ expected_metadata_intake_field_ids,
+ )
+ assert_equal(
+ f"{lane['lane_id']}.owner_response_metadata_intake_display_order",
+ [item["display_order"] for item in metadata_envelope],
+ list(range(1, len(expected_metadata_intake_field_ids) + 1)),
+ )
+ assert_equal(
+ f"{lane['lane_id']}.owner_response_metadata_intake_source_fields",
+ [item["source_required_field"] for item in metadata_envelope],
+ expected_metadata_intake_source_fields,
+ )
+ for item in metadata_envelope:
+ assert_equal(
+ f"{lane['lane_id']}.{item['field_id']}.field_status",
+ item["field_status"],
+ "waiting_owner_response_metadata",
+ )
+ assert_true(f"{lane['lane_id']}.{item['field_id']}.required", item["required"])
+ for count_key in [
+ "filled_count",
+ "received_response_count",
+ "accepted_response_count",
+ "runtime_gate_count",
+ ]:
+ assert_equal(
+ f"{lane['lane_id']}.{item['field_id']}.{count_key}",
+ item[count_key],
+ 0,
+ )
+ assert_true(
+ f"{lane['lane_id']}.{item['field_id']}.redacted_reference_required",
+ item["redacted_reference_required"],
+ )
+ for false_key in [
+ "raw_payload_allowed",
+ "secret_plaintext_allowed",
+ "action_buttons_allowed",
+ "execution_authorized",
+ ]:
+ assert_false(
+ f"{lane['lane_id']}.{item['field_id']}.{false_key}",
+ item[false_key],
+ )
+ assert_true(f"{lane['lane_id']}.{item['field_id']}.not_approval", item["not_approval"])
+ for forbidden_action in [
+ "mark_received_from_metadata_envelope",
+ "mark_accepted_from_metadata_envelope",
+ ]:
+ assert_true(
+ f"{lane['lane_id']}.{item['field_id']}.still_forbidden.{forbidden_action}",
+ forbidden_action in item["still_forbidden"],
+ )
expected_request_packet_id = lane.get("expected_request_packet_id")
if expected_request_packet_id is not None:
request_packet = snapshot["owner_response_request_packet"]