scene-capability-engine 3.6.2 → 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 +21 -0
- package/README.md +7 -2
- package/README.zh.md +7 -2
- package/bin/scene-capability-engine.js +2 -0
- package/docs/command-reference.md +39 -0
- package/lib/commands/auth.js +269 -0
- package/lib/commands/studio.js +57 -7
- 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 +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ 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
|
+
|
|
10
31
|
## [3.6.2] - 2026-03-04
|
|
11
32
|
|
|
12
33
|
### Changed
|
package/README.md
CHANGED
|
@@ -145,6 +145,11 @@ 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
|
|
|
@@ -201,5 +206,5 @@ MIT. See [LICENSE](LICENSE).
|
|
|
201
206
|
|
|
202
207
|
---
|
|
203
208
|
|
|
204
|
-
**Version**: 3.6.
|
|
205
|
-
**Last Updated**: 2026-03-
|
|
209
|
+
**Version**: 3.6.3
|
|
210
|
+
**Last Updated**: 2026-03-05
|
package/README.zh.md
CHANGED
|
@@ -145,6 +145,11 @@ 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
|
|
|
@@ -201,5 +206,5 @@ MIT,见 [LICENSE](LICENSE)。
|
|
|
201
206
|
|
|
202
207
|
---
|
|
203
208
|
|
|
204
|
-
**版本**:3.6.
|
|
205
|
-
**最后更新**: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,6 +589,11 @@ 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):
|
|
@@ -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
|
|
@@ -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
|
+
};
|
package/lib/commands/studio.js
CHANGED
|
@@ -13,6 +13,7 @@ const { captureTimelineCheckpoint } = require('../runtime/project-timeline');
|
|
|
13
13
|
const { runProblemEvaluation } = require('../problem/problem-evaluator');
|
|
14
14
|
const { TaskRefRegistry } = require('../task/task-ref-registry');
|
|
15
15
|
const { getSceStateStore } = require('../state/sce-state-store');
|
|
16
|
+
const { ensureWriteAuthorization } = require('../security/write-authorization');
|
|
16
17
|
const {
|
|
17
18
|
loadStudioIntakePolicy,
|
|
18
19
|
runStudioAutoIntake,
|
|
@@ -2661,6 +2662,12 @@ async function runStudioApplyCommand(options = {}, dependencies = {}) {
|
|
|
2661
2662
|
const job = await loadJob(paths, jobId, fileSystem);
|
|
2662
2663
|
ensureNotRolledBack(job, 'apply');
|
|
2663
2664
|
ensureStagePrerequisite(job, 'apply', 'generate');
|
|
2665
|
+
const leaseAuthResult = await ensureWriteAuthorization('studio:apply', options, {
|
|
2666
|
+
projectPath,
|
|
2667
|
+
fileSystem,
|
|
2668
|
+
env: dependencies.env,
|
|
2669
|
+
authSecret: dependencies.authSecret
|
|
2670
|
+
});
|
|
2664
2671
|
const authResult = await ensureStudioAuthorization('apply', options, {
|
|
2665
2672
|
projectPath,
|
|
2666
2673
|
fileSystem,
|
|
@@ -2696,14 +2703,22 @@ async function runStudioApplyCommand(options = {}, dependencies = {}) {
|
|
|
2696
2703
|
|
|
2697
2704
|
ensureStageCompleted(job, 'apply', {
|
|
2698
2705
|
patch_bundle_id: patchBundleId,
|
|
2699
|
-
auth_required: authResult.required,
|
|
2706
|
+
auth_required: authResult.required || leaseAuthResult.required,
|
|
2707
|
+
auth_password_required: authResult.required,
|
|
2708
|
+
auth_lease_required: leaseAuthResult.required,
|
|
2709
|
+
auth_lease_id: leaseAuthResult.lease_id || null,
|
|
2710
|
+
auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
|
|
2700
2711
|
problem_evaluation: summarizeProblemEvaluation(applyProblemEvaluation)
|
|
2701
2712
|
});
|
|
2702
2713
|
|
|
2703
2714
|
await saveJob(paths, job, fileSystem);
|
|
2704
2715
|
const applyEvent = await appendStudioEvent(paths, job, 'stage.apply.completed', {
|
|
2705
2716
|
patch_bundle_id: patchBundleId,
|
|
2706
|
-
auth_required: authResult.required,
|
|
2717
|
+
auth_required: authResult.required || leaseAuthResult.required,
|
|
2718
|
+
auth_password_required: authResult.required,
|
|
2719
|
+
auth_lease_required: leaseAuthResult.required,
|
|
2720
|
+
auth_lease_id: leaseAuthResult.lease_id || null,
|
|
2721
|
+
auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
|
|
2707
2722
|
problem_evaluation: summarizeProblemEvaluation(applyProblemEvaluation)
|
|
2708
2723
|
}, fileSystem);
|
|
2709
2724
|
await writeLatestJob(paths, jobId, fileSystem);
|
|
@@ -2890,6 +2905,12 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
|
|
|
2890
2905
|
const job = await loadJob(paths, jobId, fileSystem);
|
|
2891
2906
|
ensureNotRolledBack(job, 'release');
|
|
2892
2907
|
ensureStagePrerequisite(job, 'release', 'verify');
|
|
2908
|
+
const leaseAuthResult = await ensureWriteAuthorization('studio:release', options, {
|
|
2909
|
+
projectPath,
|
|
2910
|
+
fileSystem,
|
|
2911
|
+
env: dependencies.env,
|
|
2912
|
+
authSecret: dependencies.authSecret
|
|
2913
|
+
});
|
|
2893
2914
|
const authResult = await ensureStudioAuthorization('release', options, {
|
|
2894
2915
|
projectPath,
|
|
2895
2916
|
fileSystem,
|
|
@@ -3006,7 +3027,11 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
|
|
|
3006
3027
|
passed: false,
|
|
3007
3028
|
report: releaseReportPath,
|
|
3008
3029
|
gate_steps: gateResult.steps,
|
|
3009
|
-
auth_required: authResult.required,
|
|
3030
|
+
auth_required: authResult.required || leaseAuthResult.required,
|
|
3031
|
+
auth_password_required: authResult.required,
|
|
3032
|
+
auth_lease_required: leaseAuthResult.required,
|
|
3033
|
+
auth_lease_id: leaseAuthResult.lease_id || null,
|
|
3034
|
+
auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
|
|
3010
3035
|
problem_evaluation: summarizeProblemEvaluation(releaseProblemEvaluation),
|
|
3011
3036
|
domain_chain: domainChainMetadata,
|
|
3012
3037
|
auto_errorbook_records: autoErrorbookRecords
|
|
@@ -3017,7 +3042,11 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
|
|
|
3017
3042
|
channel,
|
|
3018
3043
|
release_ref: releaseRef,
|
|
3019
3044
|
report: releaseReportPath,
|
|
3020
|
-
auth_required: authResult.required,
|
|
3045
|
+
auth_required: authResult.required || leaseAuthResult.required,
|
|
3046
|
+
auth_password_required: authResult.required,
|
|
3047
|
+
auth_lease_required: leaseAuthResult.required,
|
|
3048
|
+
auth_lease_id: leaseAuthResult.lease_id || null,
|
|
3049
|
+
auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
|
|
3021
3050
|
problem_evaluation: summarizeProblemEvaluation(releaseProblemEvaluation),
|
|
3022
3051
|
domain_chain: domainChainMetadata,
|
|
3023
3052
|
auto_errorbook_records: autoErrorbookRecords
|
|
@@ -3032,7 +3061,11 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
|
|
|
3032
3061
|
release_ref: releaseRef,
|
|
3033
3062
|
report: releaseReportPath,
|
|
3034
3063
|
gate_steps: gateResult.steps,
|
|
3035
|
-
auth_required: authResult.required,
|
|
3064
|
+
auth_required: authResult.required || leaseAuthResult.required,
|
|
3065
|
+
auth_password_required: authResult.required,
|
|
3066
|
+
auth_lease_required: leaseAuthResult.required,
|
|
3067
|
+
auth_lease_id: leaseAuthResult.lease_id || null,
|
|
3068
|
+
auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
|
|
3036
3069
|
problem_evaluation: summarizeProblemEvaluation(releaseProblemEvaluation),
|
|
3037
3070
|
domain_chain: domainChainMetadata,
|
|
3038
3071
|
auto_errorbook_records: autoErrorbookRecords
|
|
@@ -3064,7 +3097,11 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
|
|
|
3064
3097
|
channel,
|
|
3065
3098
|
release_ref: releaseRef,
|
|
3066
3099
|
report: releaseReportPath,
|
|
3067
|
-
auth_required: authResult.required,
|
|
3100
|
+
auth_required: authResult.required || leaseAuthResult.required,
|
|
3101
|
+
auth_password_required: authResult.required,
|
|
3102
|
+
auth_lease_required: leaseAuthResult.required,
|
|
3103
|
+
auth_lease_id: leaseAuthResult.lease_id || null,
|
|
3104
|
+
auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
|
|
3068
3105
|
problem_evaluation: summarizeProblemEvaluation(releaseProblemEvaluation),
|
|
3069
3106
|
domain_chain: domainChainMetadata,
|
|
3070
3107
|
auto_errorbook_records: autoErrorbookRecords
|
|
@@ -3119,6 +3156,12 @@ async function runStudioRollbackCommand(options = {}, dependencies = {}) {
|
|
|
3119
3156
|
|
|
3120
3157
|
const reason = normalizeString(options.reason) || 'manual-rollback';
|
|
3121
3158
|
const job = await loadJob(paths, jobId, fileSystem);
|
|
3159
|
+
const leaseAuthResult = await ensureWriteAuthorization('studio:rollback', options, {
|
|
3160
|
+
projectPath,
|
|
3161
|
+
fileSystem,
|
|
3162
|
+
env: dependencies.env,
|
|
3163
|
+
authSecret: dependencies.authSecret
|
|
3164
|
+
});
|
|
3122
3165
|
const authResult = await ensureStudioAuthorization('rollback', options, {
|
|
3123
3166
|
projectPath,
|
|
3124
3167
|
fileSystem,
|
|
@@ -3134,7 +3177,11 @@ async function runStudioRollbackCommand(options = {}, dependencies = {}) {
|
|
|
3134
3177
|
job.rollback = {
|
|
3135
3178
|
reason,
|
|
3136
3179
|
rolled_back_at: job.updated_at,
|
|
3137
|
-
auth_required: authResult.required
|
|
3180
|
+
auth_required: authResult.required || leaseAuthResult.required,
|
|
3181
|
+
auth_password_required: authResult.required,
|
|
3182
|
+
auth_lease_required: leaseAuthResult.required,
|
|
3183
|
+
auth_lease_id: leaseAuthResult.lease_id || null,
|
|
3184
|
+
auth_lease_expires_at: leaseAuthResult.lease_expires_at || null
|
|
3138
3185
|
};
|
|
3139
3186
|
|
|
3140
3187
|
const sceneSessionId = normalizeString(job && job.session && job.session.scene_session_id);
|
|
@@ -3508,6 +3555,7 @@ function registerStudioCommands(program) {
|
|
|
3508
3555
|
.command('apply')
|
|
3509
3556
|
.description('Apply generated patch bundle metadata to studio job')
|
|
3510
3557
|
.option('--patch-bundle <id>', 'Patch bundle identifier (defaults to generated artifact)')
|
|
3558
|
+
.option('--auth-lease <lease-id>', 'Write authorization lease id (sce auth grant)')
|
|
3511
3559
|
.option('--auth-password <password>', 'Authorization password for protected apply action')
|
|
3512
3560
|
.option('--require-auth', 'Require authorization even when policy is advisory')
|
|
3513
3561
|
.option('--job <job-id>', 'Studio job id (defaults to latest)')
|
|
@@ -3527,6 +3575,7 @@ function registerStudioCommands(program) {
|
|
|
3527
3575
|
.description('Record release stage for studio job')
|
|
3528
3576
|
.option('--channel <channel>', 'Release channel (dev|prod)', 'dev')
|
|
3529
3577
|
.option('--profile <profile>', 'Release gate profile', 'standard')
|
|
3578
|
+
.option('--auth-lease <lease-id>', 'Write authorization lease id (sce auth grant)')
|
|
3530
3579
|
.option('--auth-password <password>', 'Authorization password for protected release action')
|
|
3531
3580
|
.option('--require-auth', 'Require authorization even when policy is advisory')
|
|
3532
3581
|
.option('--release-ref <ref>', 'Explicit release reference/tag')
|
|
@@ -3555,6 +3604,7 @@ function registerStudioCommands(program) {
|
|
|
3555
3604
|
.description('Rollback a studio job after apply/release')
|
|
3556
3605
|
.option('--job <job-id>', 'Studio job id (defaults to latest)')
|
|
3557
3606
|
.option('--reason <reason>', 'Rollback reason')
|
|
3607
|
+
.option('--auth-lease <lease-id>', 'Write authorization lease id (sce auth grant)')
|
|
3558
3608
|
.option('--auth-password <password>', 'Authorization password for protected rollback action')
|
|
3559
3609
|
.option('--require-auth', 'Require authorization even when policy is advisory')
|
|
3560
3610
|
.option('--json', 'Print machine-readable JSON output')
|
package/lib/commands/task.js
CHANGED
|
@@ -11,6 +11,7 @@ const chalk = require('chalk');
|
|
|
11
11
|
const TaskClaimer = require('../task/task-claimer');
|
|
12
12
|
const WorkspaceManager = require('../workspace/workspace-manager');
|
|
13
13
|
const { TaskRefRegistry } = require('../task/task-ref-registry');
|
|
14
|
+
const { ensureWriteAuthorization } = require('../security/write-authorization');
|
|
14
15
|
|
|
15
16
|
function normalizeString(value) {
|
|
16
17
|
if (typeof value !== 'string') {
|
|
@@ -436,6 +437,17 @@ async function runTaskRerunCommand(options = {}, dependencies = {}) {
|
|
|
436
437
|
if (!lookup) {
|
|
437
438
|
throw new Error(`Task ref not found: ${taskRef}`);
|
|
438
439
|
}
|
|
440
|
+
const writeAuthResult = dryRun
|
|
441
|
+
? {
|
|
442
|
+
required: false,
|
|
443
|
+
passed: true
|
|
444
|
+
}
|
|
445
|
+
: await ensureWriteAuthorization('task:rerun', options, {
|
|
446
|
+
projectPath,
|
|
447
|
+
fileSystem,
|
|
448
|
+
env: dependencies.env,
|
|
449
|
+
authSecret: dependencies.authSecret
|
|
450
|
+
});
|
|
439
451
|
|
|
440
452
|
if (isStudioTaskRef(lookup)) {
|
|
441
453
|
const stage = resolveStudioStageFromTaskKey(lookup.task_key);
|
|
@@ -453,7 +465,12 @@ async function runTaskRerunCommand(options = {}, dependencies = {}) {
|
|
|
453
465
|
stage,
|
|
454
466
|
job_id: normalizeString(job?.job_id) || null,
|
|
455
467
|
dry_run: dryRun,
|
|
456
|
-
command: stringifySceArgs(rerunPlan.args)
|
|
468
|
+
command: stringifySceArgs(rerunPlan.args),
|
|
469
|
+
authorization: {
|
|
470
|
+
required: writeAuthResult.required === true,
|
|
471
|
+
lease_id: writeAuthResult.lease_id || null,
|
|
472
|
+
lease_expires_at: writeAuthResult.lease_expires_at || null
|
|
473
|
+
}
|
|
457
474
|
};
|
|
458
475
|
|
|
459
476
|
if (!dryRun) {
|
|
@@ -486,7 +503,12 @@ async function runTaskRerunCommand(options = {}, dependencies = {}) {
|
|
|
486
503
|
task_ref: lookup.task_ref,
|
|
487
504
|
rerun_type: 'spec-task',
|
|
488
505
|
dry_run: dryRun,
|
|
489
|
-
command: `sce task claim ${lookup.spec_id} ${lookup.task_key}
|
|
506
|
+
command: `sce task claim ${lookup.spec_id} ${lookup.task_key}`,
|
|
507
|
+
authorization: {
|
|
508
|
+
required: writeAuthResult.required === true,
|
|
509
|
+
lease_id: writeAuthResult.lease_id || null,
|
|
510
|
+
lease_expires_at: writeAuthResult.lease_expires_at || null
|
|
511
|
+
}
|
|
490
512
|
};
|
|
491
513
|
|
|
492
514
|
if (!dryRun) {
|
|
@@ -734,6 +756,7 @@ function registerTaskCommands(program) {
|
|
|
734
756
|
.description('Rerun task by hierarchical reference')
|
|
735
757
|
.requiredOption('--ref <task-ref>', 'Task reference (SS.PP.TT)')
|
|
736
758
|
.option('--dry-run', 'Preview rerun command without executing')
|
|
759
|
+
.option('--auth-lease <lease-id>', 'Write authorization lease id (sce auth grant)')
|
|
737
760
|
.option('--from-chat <session>', 'Override session for studio plan rerun')
|
|
738
761
|
.option('--job <job-id>', 'Override studio job id')
|
|
739
762
|
.option('--profile <profile>', 'Override profile for studio verify/release rerun')
|