Skip to content
Draft
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: 10 additions & 1 deletion backend/access_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
GLOBAL_APPROVAL_DECIDE = "approval.global.decide"
ERP_EXCHANGE_EXPORT = "erp.exchange.export"
ERP_EXCHANGE_RECONCILE = "erp.exchange.reconcile"
DECISION_EVIDENCE_READ = "decision.evidence.read"
DECISION_RECORD_WRITE = "decision.record.write"

L1_MONITOR = "l1_monitor"
L2_DECISION = "l2_decision"
Expand All @@ -43,20 +45,24 @@
GLOBAL_APPROVAL_DECIDE: L3_GOVERNED_ACTION,
ERP_EXCHANGE_EXPORT: L3_GOVERNED_ACTION,
ERP_EXCHANGE_RECONCILE: L3_GOVERNED_ACTION,
DECISION_EVIDENCE_READ: L1_MONITOR,
DECISION_RECORD_WRITE: L2_DECISION,
}

_ALL_CAPABILITIES = frozenset(_CAPABILITY_ENTITLEMENT)


_ROLE_CAPABILITIES = {
"risk_viewer": frozenset({RISK_OVERVIEW_READ}),
"risk_viewer": frozenset({RISK_OVERVIEW_READ, DECISION_EVIDENCE_READ}),
"supply_planner": frozenset(
{
RISK_OVERVIEW_READ,
RISK_ANALYSIS_READ,
RISK_WHAT_IF_RUN,
RISK_WORKSPACE_WRITE,
ERP_EXCHANGE_PROPOSE,
DECISION_EVIDENCE_READ,
DECISION_RECORD_WRITE,
}
),
"procurement_approver": frozenset(
Expand All @@ -67,6 +73,7 @@
APPROVAL_DECIDE,
ERP_EXCHANGE_EXPORT,
ERP_EXCHANGE_RECONCILE,
DECISION_EVIDENCE_READ,
}
),
# Preserve the existing demo accounts while routing the new accounts
Expand All @@ -82,6 +89,8 @@
APPROVAL_QUEUE_READ,
ERP_EXCHANGE_EXPORT,
ERP_EXCHANGE_RECONCILE,
DECISION_EVIDENCE_READ,
DECISION_RECORD_WRITE,
}
),
"admin": _ALL_CAPABILITIES,
Expand Down
46 changes: 46 additions & 0 deletions backend/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,52 @@ def init_db():
version INTEGER NOT NULL DEFAULT 0
)''')

# AI 決策可驗證性:建議本身、當下證據快照與人員回饋分開保存。
# snapshot_json / snapshot_digest 一經建立不再更新,避免事後資料變動
# 讓人誤以為 AI 當時是依據新資料做出判斷。
c.execute('''CREATE TABLE IF NOT EXISTS decision_records (
decision_id TEXT PRIMARY KEY,
organization_id TEXT NOT NULL,
created_by TEXT NOT NULL,
decision_type TEXT NOT NULL,
model_name TEXT NOT NULL,
model_output_json TEXT NOT NULL,
output_schema_version INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'proposed',
decision_reason TEXT NOT NULL DEFAULT '',
decided_by TEXT,
decided_at TEXT,
created_at TEXT NOT NULL
)''')
c.execute('''CREATE TABLE IF NOT EXISTS decision_evidence_snapshots (
decision_id TEXT PRIMARY KEY,
snapshot_json TEXT NOT NULL,
snapshot_digest TEXT NOT NULL,
data_as_of TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY(decision_id) REFERENCES decision_records(decision_id)
)''')
c.execute('''CREATE TABLE IF NOT EXISTS decision_feedback (
feedback_id INTEGER PRIMARY KEY AUTOINCREMENT,
decision_id TEXT NOT NULL,
outcome TEXT NOT NULL,
note TEXT NOT NULL DEFAULT '',
action_taken TEXT NOT NULL DEFAULT '',
outcome_evidence TEXT NOT NULL DEFAULT '',
recorded_by TEXT NOT NULL,
recorded_at TEXT NOT NULL,
FOREIGN KEY(decision_id) REFERENCES decision_records(decision_id)
)''')
c.execute('''CREATE INDEX IF NOT EXISTS ix_decision_records_created
ON decision_records(organization_id, created_at DESC)''')

# 讓已建立的舊資料庫也可逐步加入較完整的回饋欄位。
for column_name in ("action_taken", "outcome_evidence"):
try:
c.execute(f"ALTER TABLE decision_feedback ADD COLUMN {column_name} TEXT NOT NULL DEFAULT ''")
except sqlite3.OperationalError:
pass

c.execute('''CREATE TABLE IF NOT EXISTS effect_receipts (
receipt_id INTEGER PRIMARY KEY AUTOINCREMENT,
operation_id TEXT NOT NULL UNIQUE,
Expand Down
Loading
Loading