scene-capability-engine 3.6.0 → 3.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +9 -3
- package/README.zh.md +9 -3
- package/bin/scene-capability-engine.js +2 -0
- package/docs/command-reference.md +40 -1
- package/docs/release-checklist.md +3 -3
- package/docs/zh/release-checklist.md +3 -3
- package/lib/commands/auth.js +269 -0
- package/lib/commands/studio.js +190 -8
- package/lib/commands/task.js +25 -2
- package/lib/security/write-authorization.js +632 -0
- package/lib/state/sce-state-store.js +400 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.6.3] - 2026-03-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- SQLite-backed write authorization lease flow:
|
|
14
|
+
- new policy baseline: `.sce/config/authorization-policy.json`
|
|
15
|
+
- new state tables: `auth_lease_registry`, `auth_event_stream`
|
|
16
|
+
- new commands:
|
|
17
|
+
- `sce auth grant`
|
|
18
|
+
- `sce auth status`
|
|
19
|
+
- `sce auth revoke`
|
|
20
|
+
- New write-authorization module: `lib/security/write-authorization.js`.
|
|
21
|
+
- New unit coverage:
|
|
22
|
+
- `tests/unit/security/write-authorization.test.js`
|
|
23
|
+
- `tests/unit/commands/auth.test.js`
|
|
24
|
+
- extended state-store auth lifecycle tests.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- `studio apply/release/rollback` now support `--auth-lease <lease-id>` and can enforce lease-based write authorization.
|
|
28
|
+
- `task rerun` now supports `--auth-lease <lease-id>` and can enforce lease-based write authorization on non-dry-run operations.
|
|
29
|
+
- Authorization checks and lease audit events are persisted to `.sce/state/sce-state.sqlite`.
|
|
30
|
+
|
|
31
|
+
## [3.6.2] - 2026-03-04
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Release default test gate now runs integration tests only (`npm run test:release`) for faster online publish verification.
|
|
35
|
+
- `prepublishOnly` now uses `test:release` instead of `test:full`.
|
|
36
|
+
- GitHub `release.yml` test job now runs integration-only release tests and skips coverage in release path.
|
|
37
|
+
- Studio task-stream schema now includes normalized task-intent fields (`title_norm`, `raw_request`, `sub_goals`, `acceptance_criteria`, `needs_split`, `confidence`) while preserving existing task fields for compatibility.
|
|
38
|
+
- Added version CLI integration coverage (`tests/integration/version-cli.integration.test.js`) to ensure `--version` output remains deterministic and package-aligned.
|
|
39
|
+
|
|
10
40
|
## [3.6.0] - 2026-03-04
|
|
11
41
|
|
|
12
42
|
### Added
|
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ SCE is tool-agnostic and works with Codex, Claude Code, Cursor, Windsurf, VS Cod
|
|
|
130
130
|
|
|
131
131
|
Studio task-stream output contract (default):
|
|
132
132
|
- IDs: `sessionId`, `sceneId`, `specId`, `taskId`, `taskRef`, `eventId`
|
|
133
|
-
- Task: `task.goal`, `task.status`, `task.summary` (3-line), `task.handoff`, `task.next_action`
|
|
133
|
+
- Task: `task.task_ref`, `task.title_norm`, `task.raw_request`, `task.goal`, `task.sub_goals`, `task.acceptance_criteria`, `task.needs_split`, `task.confidence`, `task.status`, `task.summary` (3-line), `task.handoff`, `task.next_action`
|
|
134
134
|
- File refs: `task.file_changes[]` with `path`, `line`, `diffRef`
|
|
135
135
|
- Command logs: `task.commands[]` with `cmd`, `exit_code`, `stdout`, `stderr`, `log_path`
|
|
136
136
|
- Errors: `task.errors[]` with `message`, `error_bundle` (copy-ready)
|
|
@@ -145,11 +145,17 @@ Studio task-stream output contract (default):
|
|
|
145
145
|
- SQLite-only backend (`.sce/state/sce-state.sqlite`)
|
|
146
146
|
- In-memory fallback only in `NODE_ENV=test` or when `SCE_STATE_ALLOW_MEMORY_FALLBACK=1`
|
|
147
147
|
- Outside those conditions, unavailable SQLite support fails fast for task-ref/event persistence
|
|
148
|
+
- Write authorization lease model (SQLite-backed):
|
|
149
|
+
- policy file: `.sce/config/authorization-policy.json`
|
|
150
|
+
- grant lease: `sce auth grant --scope studio:* --reason "<reason>" --auth-password <password> --json`
|
|
151
|
+
- inspect/revoke: `sce auth status --json` / `sce auth revoke --lease <lease-id> --json`
|
|
152
|
+
- protected writes accept `--auth-lease <lease-id>` on `studio apply/release/rollback` and `task rerun`
|
|
148
153
|
|
|
149
154
|
---
|
|
150
155
|
|
|
151
156
|
## Important Version Changes
|
|
152
157
|
|
|
158
|
+
- `3.6.2`: Added release-level version integration tests (`tests/integration/version-cli.integration.test.js`) and switched release default verification to integration-only gate (`npm run test:release`) for faster publish feedback.
|
|
153
159
|
- `3.6.0`: Added hierarchical task references (`taskRef`, format `SS.PP.TT`) backed by SQLite state store `.sce/state/sce-state.sqlite`, plus new task commands (`sce task ref/show/rerun`) for reference lookup and deterministic rerun.
|
|
154
160
|
- `3.5.2`: Introduced task-stream output contract for Studio commands (`sessionId/sceneId/specId/taskId/eventId`, structured `task.*` fields, `event[]` audit stream) and added OpenHands raw-event bridge via `sce studio events --openhands-events <path>`.
|
|
155
161
|
- `3.5.1`: Enforced stricter Studio intake defaults (`--manual-spec` and `--no-spec-governance` blocked unless policy override), added historical spec scene backfill command (`sce studio backfill-spec-scenes`) and persisted override mapping (`.sce/spec-governance/spec-scene-overrides.json`) for portfolio/related-spec alignment.
|
|
@@ -200,5 +206,5 @@ MIT. See [LICENSE](LICENSE).
|
|
|
200
206
|
|
|
201
207
|
---
|
|
202
208
|
|
|
203
|
-
**Version**: 3.6.
|
|
204
|
-
**Last Updated**: 2026-03-
|
|
209
|
+
**Version**: 3.6.3
|
|
210
|
+
**Last Updated**: 2026-03-05
|
package/README.zh.md
CHANGED
|
@@ -130,7 +130,7 @@ SCE 对工具无锁定,可接入 Codex、Claude Code、Cursor、Windsurf、VS
|
|
|
130
130
|
|
|
131
131
|
Studio 任务流输出契约(默认):
|
|
132
132
|
- ID 字段:`sessionId`、`sceneId`、`specId`、`taskId`、`taskRef`、`eventId`
|
|
133
|
-
- 任务主体:`task.goal`、`task.status`、`task.summary`(固定三行)、`task.handoff`、`task.next_action`
|
|
133
|
+
- 任务主体:`task.task_ref`、`task.title_norm`、`task.raw_request`、`task.goal`、`task.sub_goals`、`task.acceptance_criteria`、`task.needs_split`、`task.confidence`、`task.status`、`task.summary`(固定三行)、`task.handoff`、`task.next_action`
|
|
134
134
|
- 文件引用:`task.file_changes[]`(`path`、`line`、`diffRef`)
|
|
135
135
|
- 命令执行:`task.commands[]`(`cmd`、`exit_code`、`stdout`、`stderr`、`log_path`)
|
|
136
136
|
- 错误复制:`task.errors[]`(`message`、`error_bundle`,可直接复制给 AI 修复)
|
|
@@ -145,11 +145,17 @@ Studio 任务流输出契约(默认):
|
|
|
145
145
|
- 仅支持 SQLite 后端(`.sce/state/sce-state.sqlite`)
|
|
146
146
|
- 仅在 `NODE_ENV=test` 或 `SCE_STATE_ALLOW_MEMORY_FALLBACK=1` 时允许内存回退
|
|
147
147
|
- 在上述条件之外若 SQLite 不可用,任务引用/事件持久化会快速失败
|
|
148
|
+
- 写入授权租约模型(SQLite 持久化):
|
|
149
|
+
- 策略文件:`.sce/config/authorization-policy.json`
|
|
150
|
+
- 申请租约:`sce auth grant --scope studio:* --reason "<原因>" --auth-password <密码> --json`
|
|
151
|
+
- 查看/撤销:`sce auth status --json` / `sce auth revoke --lease <lease-id> --json`
|
|
152
|
+
- 受保护写操作支持 `--auth-lease <lease-id>`:`studio apply/release/rollback`、`task rerun`
|
|
148
153
|
|
|
149
154
|
---
|
|
150
155
|
|
|
151
156
|
## 重要版本变更
|
|
152
157
|
|
|
158
|
+
- `3.6.2`:新增发布版本号集成测试(`tests/integration/version-cli.integration.test.js`),并将发布默认验证切换为仅 integration 门禁(`npm run test:release`),加速发布反馈。
|
|
153
159
|
- `3.6.0`:新增分层任务引用(`taskRef`,格式 `SS.PP.TT`),持久化到 SQLite 状态库 `.sce/state/sce-state.sqlite`;新增 `sce task ref/show/rerun` 用于引用查询与可重放执行。
|
|
154
160
|
- `3.5.2`:新增 Studio 任务流输出契约(`sessionId/sceneId/specId/taskId/eventId`、结构化 `task.*` 字段、`event[]` 审计流),并新增 OpenHands 原始事件桥接能力:`sce studio events --openhands-events <path>`。
|
|
155
161
|
- `3.5.1`:默认强化 Studio intake 治理(`--manual-spec`、`--no-spec-governance` 在未显式放开策略时会被阻断),新增历史 spec 场景回填命令 `sce studio backfill-spec-scenes`,并写入 `.sce/spec-governance/spec-scene-overrides.json` 以统一 portfolio 与 related-spec 的场景映射。
|
|
@@ -200,5 +206,5 @@ MIT,见 [LICENSE](LICENSE)。
|
|
|
200
206
|
|
|
201
207
|
---
|
|
202
208
|
|
|
203
|
-
**版本**:3.6.
|
|
204
|
-
**最后更新**:2026-03-
|
|
209
|
+
**版本**:3.6.3
|
|
210
|
+
**最后更新**:2026-03-05
|
|
@@ -24,6 +24,7 @@ const { registerSpecRelatedCommand } = require('../lib/commands/spec-related');
|
|
|
24
24
|
const { registerTimelineCommands } = require('../lib/commands/timeline');
|
|
25
25
|
const { registerValueCommands } = require('../lib/commands/value');
|
|
26
26
|
const { registerTaskCommands } = require('../lib/commands/task');
|
|
27
|
+
const { registerAuthCommands } = require('../lib/commands/auth');
|
|
27
28
|
const VersionChecker = require('../lib/version/version-checker');
|
|
28
29
|
const {
|
|
29
30
|
findLegacyKiroDirectories,
|
|
@@ -945,6 +946,7 @@ registerOrchestrateCommands(program);
|
|
|
945
946
|
// Value realization and observability commands
|
|
946
947
|
registerValueCommands(program);
|
|
947
948
|
registerTaskCommands(program);
|
|
949
|
+
registerAuthCommands(program);
|
|
948
950
|
|
|
949
951
|
// Template management commands
|
|
950
952
|
const templatesCommand = require('../lib/commands/templates');
|
|
@@ -552,6 +552,8 @@ sce studio generate --scene scene.customer-order-inventory --target 331 --json
|
|
|
552
552
|
|
|
553
553
|
# Apply generated patch metadata
|
|
554
554
|
sce studio apply --patch-bundle patch-scene.customer-order-inventory-<timestamp> --json
|
|
555
|
+
# Apply with explicit write lease
|
|
556
|
+
sce studio apply --job <job-id> --auth-lease <lease-id> --json
|
|
555
557
|
|
|
556
558
|
# Record verification result
|
|
557
559
|
sce studio verify --profile standard --json
|
|
@@ -560,6 +562,8 @@ sce studio verify --profile strict --json
|
|
|
560
562
|
# Record release event
|
|
561
563
|
sce studio release --channel dev --profile standard --json
|
|
562
564
|
sce studio release --channel dev --profile strict --json
|
|
565
|
+
# Release with explicit write lease
|
|
566
|
+
sce studio release --job <job-id> --channel dev --auth-lease <lease-id> --json
|
|
563
567
|
|
|
564
568
|
# Resume from latest or explicit job
|
|
565
569
|
sce studio resume --job <job-id> --json
|
|
@@ -571,6 +575,8 @@ sce studio events --job <job-id> --openhands-events ./openhands-events.json --js
|
|
|
571
575
|
|
|
572
576
|
# Rollback a job after apply/release
|
|
573
577
|
sce studio rollback --job <job-id> --reason "manual-check-failed" --json
|
|
578
|
+
# Rollback with explicit write lease
|
|
579
|
+
sce studio rollback --job <job-id> --reason "manual-check-failed" --auth-lease <lease-id> --json
|
|
574
580
|
|
|
575
581
|
# Build scene-organized spec governance portfolio
|
|
576
582
|
sce studio portfolio --json
|
|
@@ -583,11 +589,16 @@ sce studio backfill-spec-scenes --scene scene.unassigned --limit 20 --apply --js
|
|
|
583
589
|
|
|
584
590
|
# Enforce authorization for a protected action
|
|
585
591
|
SCE_STUDIO_REQUIRE_AUTH=1 SCE_STUDIO_AUTH_PASSWORD=top-secret sce studio apply --job <job-id> --auth-password top-secret --json
|
|
592
|
+
|
|
593
|
+
# Grant/review/revoke write lease (stored in .sce/state/sce-state.sqlite)
|
|
594
|
+
SCE_AUTH_PASSWORD=top-secret sce auth grant --scope studio:* --reason "maintenance window" --auth-password top-secret --json
|
|
595
|
+
sce auth status --json
|
|
596
|
+
sce auth revoke --lease <lease-id> --reason "window closed" --json
|
|
586
597
|
```
|
|
587
598
|
|
|
588
599
|
Studio JSON output now includes a stable UI-oriented task stream contract (in addition to existing `job_*` fields):
|
|
589
600
|
- root IDs: `sessionId`, `sceneId`, `specId`, `taskId`, `taskRef`, `eventId`
|
|
590
|
-
- `task.goal`, `task.status`, `task.summary` (fixed 3-line summary), `task.handoff`, `task.next_action`
|
|
601
|
+
- `task.task_ref`, `task.title_norm`, `task.raw_request`, `task.goal`, `task.sub_goals`, `task.acceptance_criteria`, `task.needs_split`, `task.confidence`, `task.status`, `task.summary` (fixed 3-line summary), `task.handoff`, `task.next_action`
|
|
591
602
|
- `task.file_changes[]`: `path`, `line`, `diffRef`
|
|
592
603
|
- `task.commands[]`: `cmd`, `exit_code`, `stdout`, `stderr`, `log_path`
|
|
593
604
|
- `task.errors[]`: `message`, `error_bundle` (copy-ready diagnostic bundle)
|
|
@@ -689,6 +700,34 @@ Default policy file (recommended to commit): `.sce/config/studio-security.json`
|
|
|
689
700
|
}
|
|
690
701
|
```
|
|
691
702
|
|
|
703
|
+
Write lease model (optional, policy-driven, SQLite-backed):
|
|
704
|
+
- Policy file: `.sce/config/authorization-policy.json`
|
|
705
|
+
- Lease/event persistence: `.sce/state/sce-state.sqlite` (`auth_lease_registry`, `auth_event_stream`)
|
|
706
|
+
- Default protected actions: `studio:apply`, `studio:release`, `studio:rollback`, `task:rerun`
|
|
707
|
+
- Default lease TTL: 15 minutes (`default_ttl_minutes`)
|
|
708
|
+
- Write commands accept `--auth-lease <lease-id>`
|
|
709
|
+
- Policy env overrides:
|
|
710
|
+
- `SCE_AUTH_REQUIRE_LEASE=1`
|
|
711
|
+
- `SCE_AUTH_PASSWORD_ENV=<ENV_NAME>`
|
|
712
|
+
- `SCE_AUTH_ENFORCE_ACTIONS=studio:apply,task:rerun`
|
|
713
|
+
|
|
714
|
+
Default write authorization policy (recommended to commit): `.sce/config/authorization-policy.json`
|
|
715
|
+
|
|
716
|
+
```json
|
|
717
|
+
{
|
|
718
|
+
"enabled": false,
|
|
719
|
+
"enforce_actions": ["studio:apply", "studio:release", "studio:rollback", "task:rerun"],
|
|
720
|
+
"default_ttl_minutes": 15,
|
|
721
|
+
"max_ttl_minutes": 120,
|
|
722
|
+
"require_password_for_grant": true,
|
|
723
|
+
"require_password_for_revoke": false,
|
|
724
|
+
"password_env": "SCE_AUTH_PASSWORD",
|
|
725
|
+
"default_scope": ["project:*"],
|
|
726
|
+
"allow_test_bypass": true,
|
|
727
|
+
"allow_password_as_inline_lease": false
|
|
728
|
+
}
|
|
729
|
+
```
|
|
730
|
+
|
|
692
731
|
Studio intake policy file (default, recommended to commit): `.sce/config/studio-intake-policy.json`
|
|
693
732
|
|
|
694
733
|
```json
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
## 1. Functional Verification
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
#
|
|
11
|
-
npm run test:
|
|
10
|
+
# Default release test gate (integration-only)
|
|
11
|
+
npm run test:release
|
|
12
12
|
|
|
13
|
-
#
|
|
13
|
+
# Optional full regression suite (unit + integration + properties)
|
|
14
14
|
npm run test:full
|
|
15
15
|
|
|
16
16
|
# Guardrail: fail on newly introduced .skip tests
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
## 1. 功能验证
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
#
|
|
11
|
-
npm run test:
|
|
10
|
+
# 默认发布测试门禁(仅 integration)
|
|
11
|
+
npm run test:release
|
|
12
12
|
|
|
13
|
-
#
|
|
13
|
+
# 可选全量回归(unit + integration + properties)
|
|
14
14
|
npm run test:full
|
|
15
15
|
|
|
16
16
|
# 防回归:禁止新增 .skip 测试
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const {
|
|
4
|
+
grantWriteAuthorizationLease,
|
|
5
|
+
revokeWriteAuthorizationLease,
|
|
6
|
+
collectWriteAuthorizationStatus,
|
|
7
|
+
getWriteAuthorizationLease
|
|
8
|
+
} = require('../security/write-authorization');
|
|
9
|
+
|
|
10
|
+
function normalizeString(value) {
|
|
11
|
+
if (typeof value !== 'string') {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
return value.trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeInteger(value, fallback = 0) {
|
|
18
|
+
const parsed = Number.parseInt(`${value}`, 10);
|
|
19
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeScopeInput(value) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
const text = normalizeString(value);
|
|
30
|
+
if (!text) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
return text.split(/[,\s]+/g).map((item) => normalizeString(item)).filter(Boolean);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function printAuthPayload(payload, options = {}) {
|
|
37
|
+
if (options.json) {
|
|
38
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const mode = normalizeString(payload.mode);
|
|
43
|
+
if (mode === 'auth-grant') {
|
|
44
|
+
console.log(chalk.blue(`Auth lease granted: ${payload.lease.lease_id}`));
|
|
45
|
+
console.log(` Subject: ${payload.lease.subject}`);
|
|
46
|
+
console.log(` Role: ${payload.lease.role}`);
|
|
47
|
+
console.log(` Scope: ${(payload.lease.scope || []).join(', ') || 'n/a'}`);
|
|
48
|
+
console.log(` Expires: ${payload.lease.expires_at || 'n/a'}`);
|
|
49
|
+
console.log(` Store: ${payload.store_path || 'n/a'}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (mode === 'auth-revoke') {
|
|
54
|
+
console.log(chalk.blue(`Auth lease revoked: ${payload.lease.lease_id}`));
|
|
55
|
+
console.log(` Subject: ${payload.lease.subject}`);
|
|
56
|
+
console.log(` Revoked at: ${payload.lease.revoked_at || 'n/a'}`);
|
|
57
|
+
console.log(` Store: ${payload.store_path || 'n/a'}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (mode === 'auth-status') {
|
|
62
|
+
if (payload.lease) {
|
|
63
|
+
console.log(chalk.blue(`Auth lease: ${payload.lease.lease_id}`));
|
|
64
|
+
console.log(` Subject: ${payload.lease.subject}`);
|
|
65
|
+
console.log(` Role: ${payload.lease.role}`);
|
|
66
|
+
console.log(` Scope: ${(payload.lease.scope || []).join(', ') || 'n/a'}`);
|
|
67
|
+
console.log(` Expires: ${payload.lease.expires_at || 'n/a'}`);
|
|
68
|
+
console.log(` Revoked: ${payload.lease.revoked_at || 'no'}`);
|
|
69
|
+
} else {
|
|
70
|
+
console.log(chalk.blue('Auth lease status'));
|
|
71
|
+
console.log(` Active leases: ${payload.summary.active_lease_count}`);
|
|
72
|
+
console.log(` Events: ${payload.summary.event_count}`);
|
|
73
|
+
}
|
|
74
|
+
console.log(` Store: ${payload.store_path || 'n/a'}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function runAuthGrantCommand(options = {}, dependencies = {}) {
|
|
79
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
80
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
81
|
+
const env = dependencies.env || process.env;
|
|
82
|
+
|
|
83
|
+
const granted = await grantWriteAuthorizationLease({
|
|
84
|
+
subject: options.subject,
|
|
85
|
+
role: options.role,
|
|
86
|
+
scope: normalizeScopeInput(options.scope),
|
|
87
|
+
ttlMinutes: normalizeInteger(options.ttlMinutes, 15),
|
|
88
|
+
reason: options.reason,
|
|
89
|
+
authPassword: options.authPassword,
|
|
90
|
+
actor: options.actor,
|
|
91
|
+
metadata: {
|
|
92
|
+
source: 'sce auth grant'
|
|
93
|
+
}
|
|
94
|
+
}, {
|
|
95
|
+
projectPath,
|
|
96
|
+
fileSystem,
|
|
97
|
+
env,
|
|
98
|
+
authSecret: dependencies.authSecret
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const payload = {
|
|
102
|
+
mode: 'auth-grant',
|
|
103
|
+
success: true,
|
|
104
|
+
policy: granted.policy,
|
|
105
|
+
policy_path: granted.policy_path,
|
|
106
|
+
lease: granted.lease,
|
|
107
|
+
store_path: granted.store_path
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
printAuthPayload(payload, options);
|
|
111
|
+
return payload;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function runAuthStatusCommand(options = {}, dependencies = {}) {
|
|
115
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
116
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
117
|
+
const env = dependencies.env || process.env;
|
|
118
|
+
const leaseId = normalizeString(options.lease);
|
|
119
|
+
|
|
120
|
+
if (leaseId) {
|
|
121
|
+
const [status, lease] = await Promise.all([
|
|
122
|
+
collectWriteAuthorizationStatus({
|
|
123
|
+
activeOnly: options.all !== true,
|
|
124
|
+
limit: normalizeInteger(options.limit, 20),
|
|
125
|
+
eventsLimit: normalizeInteger(options.eventsLimit, 20)
|
|
126
|
+
}, {
|
|
127
|
+
projectPath,
|
|
128
|
+
fileSystem,
|
|
129
|
+
env
|
|
130
|
+
}),
|
|
131
|
+
getWriteAuthorizationLease(leaseId, {
|
|
132
|
+
projectPath,
|
|
133
|
+
fileSystem,
|
|
134
|
+
env
|
|
135
|
+
})
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
const payload = {
|
|
139
|
+
mode: 'auth-status',
|
|
140
|
+
success: true,
|
|
141
|
+
policy: status.policy,
|
|
142
|
+
policy_path: status.policy_path,
|
|
143
|
+
lease: lease || null,
|
|
144
|
+
summary: {
|
|
145
|
+
active_lease_count: Array.isArray(status.leases) ? status.leases.length : 0,
|
|
146
|
+
event_count: Array.isArray(status.events) ? status.events.length : 0
|
|
147
|
+
},
|
|
148
|
+
events: Array.isArray(status.events) ? status.events : [],
|
|
149
|
+
store_path: status.store_path
|
|
150
|
+
};
|
|
151
|
+
printAuthPayload(payload, options);
|
|
152
|
+
return payload;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const status = await collectWriteAuthorizationStatus({
|
|
156
|
+
activeOnly: options.all !== true,
|
|
157
|
+
limit: normalizeInteger(options.limit, 20),
|
|
158
|
+
eventsLimit: normalizeInteger(options.eventsLimit, 20)
|
|
159
|
+
}, {
|
|
160
|
+
projectPath,
|
|
161
|
+
fileSystem,
|
|
162
|
+
env
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const payload = {
|
|
166
|
+
mode: 'auth-status',
|
|
167
|
+
success: true,
|
|
168
|
+
policy: status.policy,
|
|
169
|
+
policy_path: status.policy_path,
|
|
170
|
+
leases: Array.isArray(status.leases) ? status.leases : [],
|
|
171
|
+
events: Array.isArray(status.events) ? status.events : [],
|
|
172
|
+
summary: {
|
|
173
|
+
active_lease_count: Array.isArray(status.leases) ? status.leases.length : 0,
|
|
174
|
+
event_count: Array.isArray(status.events) ? status.events.length : 0
|
|
175
|
+
},
|
|
176
|
+
store_path: status.store_path
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
printAuthPayload(payload, options);
|
|
180
|
+
return payload;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function runAuthRevokeCommand(options = {}, dependencies = {}) {
|
|
184
|
+
const leaseId = normalizeString(options.lease);
|
|
185
|
+
if (!leaseId) {
|
|
186
|
+
throw new Error('--lease is required');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
190
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
191
|
+
const env = dependencies.env || process.env;
|
|
192
|
+
|
|
193
|
+
const revoked = await revokeWriteAuthorizationLease(leaseId, {
|
|
194
|
+
authPassword: options.authPassword,
|
|
195
|
+
reason: options.reason,
|
|
196
|
+
actor: options.actor
|
|
197
|
+
}, {
|
|
198
|
+
projectPath,
|
|
199
|
+
fileSystem,
|
|
200
|
+
env,
|
|
201
|
+
authSecret: dependencies.authSecret
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const payload = {
|
|
205
|
+
mode: 'auth-revoke',
|
|
206
|
+
success: true,
|
|
207
|
+
policy: revoked.policy,
|
|
208
|
+
policy_path: revoked.policy_path,
|
|
209
|
+
lease: revoked.lease,
|
|
210
|
+
store_path: revoked.store_path
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
printAuthPayload(payload, options);
|
|
214
|
+
return payload;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function runAuthCommand(handler, options = {}, context = 'auth') {
|
|
218
|
+
Promise.resolve(handler(options))
|
|
219
|
+
.catch((error) => {
|
|
220
|
+
console.error(chalk.red(`${context} failed: ${error.message}`));
|
|
221
|
+
process.exitCode = 1;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function registerAuthCommands(program) {
|
|
226
|
+
const auth = program
|
|
227
|
+
.command('auth')
|
|
228
|
+
.description('Manage temporary write authorization leases');
|
|
229
|
+
|
|
230
|
+
auth
|
|
231
|
+
.command('grant')
|
|
232
|
+
.description('Grant a write authorization lease (persisted in sqlite)')
|
|
233
|
+
.option('--subject <subject>', 'Lease subject (default: current user)')
|
|
234
|
+
.option('--role <role>', 'Subject role', 'maintainer')
|
|
235
|
+
.option('--scope <scope>', 'Scope list, comma-separated (example: studio:*,task:rerun)')
|
|
236
|
+
.option('--ttl-minutes <minutes>', 'Lease TTL in minutes', '15')
|
|
237
|
+
.option('--reason <reason>', 'Grant reason')
|
|
238
|
+
.option('--actor <actor>', 'Audit actor override')
|
|
239
|
+
.option('--auth-password <password>', 'Authorization password for grant policy')
|
|
240
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
241
|
+
.action((options) => runAuthCommand(runAuthGrantCommand, options, 'auth grant'));
|
|
242
|
+
|
|
243
|
+
auth
|
|
244
|
+
.command('status')
|
|
245
|
+
.description('Show current authorization lease and event status from sqlite')
|
|
246
|
+
.option('--lease <lease-id>', 'Inspect one lease id')
|
|
247
|
+
.option('--all', 'Include inactive/revoked leases')
|
|
248
|
+
.option('--limit <n>', 'Lease result limit', '20')
|
|
249
|
+
.option('--events-limit <n>', 'Auth event result limit', '20')
|
|
250
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
251
|
+
.action((options) => runAuthCommand(runAuthStatusCommand, options, 'auth status'));
|
|
252
|
+
|
|
253
|
+
auth
|
|
254
|
+
.command('revoke')
|
|
255
|
+
.description('Revoke a write authorization lease')
|
|
256
|
+
.requiredOption('--lease <lease-id>', 'Lease id')
|
|
257
|
+
.option('--reason <reason>', 'Revoke reason')
|
|
258
|
+
.option('--actor <actor>', 'Audit actor override')
|
|
259
|
+
.option('--auth-password <password>', 'Authorization password for revoke policy')
|
|
260
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
261
|
+
.action((options) => runAuthCommand(runAuthRevokeCommand, options, 'auth revoke'));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = {
|
|
265
|
+
runAuthGrantCommand,
|
|
266
|
+
runAuthStatusCommand,
|
|
267
|
+
runAuthRevokeCommand,
|
|
268
|
+
registerAuthCommands
|
|
269
|
+
};
|