@piagent/platform 1.1.6 → 1.1.8
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 +22 -0
- package/README.md +1 -1
- package/docs/capability-packs.md +22 -1
- package/docs/command-reference-vietnamese.md +3 -2
- package/docs/distribution-standard.md +7 -7
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/project-adapters.md +23 -0
- package/docs/publishing-for-teams.md +2 -0
- package/docs/release-install-policy.md +6 -6
- package/docs/security-threat-model.md +11 -1
- package/docs/team-onboarding.md +11 -3
- package/docs-site/index.html +2 -2
- package/package.json +2 -2
- package/packages/piagent-core/capabilities/capability-core.js +110 -8
- package/packages/piagent-core/capabilities/project-profile.js +83 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +119 -26
- package/packages/piagent-core/extensions/update-check.js +170 -0
- package/packages/piagent-core/package.json +1 -1
- package/schemas/project-profile.schema.json +25 -7
- package/scripts/capability-catalog.mjs +28 -7
- package/scripts/init-project.sh +7 -0
- package/scripts/install-global.sh +3 -3
- package/scripts/profile-doctor.sh +13 -3
- package/scripts/runtime-policy-smoke.sh +12 -3
- package/scripts/team-doctor.sh +12 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
This file records release-facing changes for Pi Agent Platform. Copy the relevant version block into GitHub Releases when publishing a tag.
|
|
4
4
|
|
|
5
|
+
## v1.1.8 - 2026-07-28
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Pi says when a release is out, and names the command that takes it. The notice appears in the session-start line as `Piagent update available: 1.1.7 -> 1.1.8. Run \`piagent-update\` to move this machine to it.` Nobody has to remember to go and check, which until now was the only way to find out.
|
|
10
|
+
|
|
11
|
+
It costs the session nothing. The session reads a cache and, when that cache is older than a day, leaves a detached `npm view` behind to refresh it — so the first session after a release may still be quiet and the next one carries the notice. This is the only network traffic the guard originates, it sends nothing about the project, and the registry's answer reaches the terminal only if it is exactly `MAJOR.MINOR.PATCH`, so neither a hostile registry nor an edited cache can put chosen text in front of an operator. A checkout is never told to update. `PIAGENT_NO_UPDATE_CHECK=1` switches it off.
|
|
12
|
+
|
|
13
|
+
## v1.1.7 - 2026-07-28
|
|
14
|
+
|
|
15
|
+
A platform update is one global install. It was not behaving like one: every project that selected capability packs stopped working after a release until its profile was reapplied by hand, and the policy a release corrected never reached a project that had already onboarded.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- A capability lock that is behind the installed platform is re-pinned instead of refusing the session. The lock pinned two different things and gave them one answer. What the project consented to — which packs, from where, and how far they may reach — still blocks: a grant that widens, a protection that disappears, a pack that changes identity. Which build produced the resolution — package version, runtime file digests, artifact content — moves on every release, and blocking on it put a per-project chore behind a global update while not defending against the case it resembles, since the code that verifies the lock is itself one of the files the lock pins. A build move with an unchanged grant is recorded and reported.
|
|
20
|
+
|
|
21
|
+
- Artifact content is part of the build, not part of consent. Adapter policy files, base policy, prompts, skills, subagents and recipes all ship as pack artifacts, so pinning their bytes meant a corrected policy could never reach a project referencing it. Nothing is given up: which artifacts a pack provides is declared in its manifest and the manifest digest is still pinned, so an artifact that appears, disappears or moves still blocks — and a policy artifact that weakens still blocks, because the permissions it resolves to are compared directionally.
|
|
22
|
+
|
|
23
|
+
- A project profile can name the adapter it takes its policy from instead of copying it. `{"extends": "web-frontend", "projectId": ..., "displayName": ...}` keeps the project's identity and overrides in the project, and resolves the rest from the installed platform at runtime. Any key the project states replaces the adapter's key whole; a profile without `extends` keeps working exactly as before, self-contained. `init-project.sh` and `/onboard-project` write this form when the profile comes from a built-in adapter, so onboarding stays the one per-project step and updating stays global.
|
|
24
|
+
|
|
25
|
+
A profile naming an adapter that is not installed refuses rather than falling back to something weaker.
|
|
26
|
+
|
|
5
27
|
## v1.1.6 - 2026-07-27
|
|
6
28
|
|
|
7
29
|
Monorepo fixes found auditing how the platform scopes itself to a project. Every case was reproduced against a real directory tree before it was changed.
|
package/README.md
CHANGED
|
@@ -283,7 +283,7 @@ This repository intentionally excludes:
|
|
|
283
283
|
|
|
284
284
|
## Maturity
|
|
285
285
|
|
|
286
|
-
The current package version is read from package metadata and release tags. Personal machines may follow the unpinned package source when accepting ongoing updates; production/team quickstarts and committed project settings should pin an explicit tag such as `v1.1.
|
|
286
|
+
The current package version is read from package metadata and release tags. Personal machines may follow the unpinned package source when accepting ongoing updates; production/team quickstarts and committed project settings should pin an explicit tag such as `v1.1.8` or a reviewed commit.
|
|
287
287
|
|
|
288
288
|
Ready for:
|
|
289
289
|
|
package/docs/capability-packs.md
CHANGED
|
@@ -153,6 +153,7 @@ Pack không thể cấp thêm quyền cho chính nó:
|
|
|
153
153
|
- Doctor phát hiện catalog hoặc lock stale.
|
|
154
154
|
- Generated output dùng temporary file cùng thư mục, `fsync`, rồi atomic rename.
|
|
155
155
|
- Lock gắn với declared package-source identifier, package version, catalog và digest của các runtime enforcement file đang cài. Đây là integrity binding của runtime quan sát được, không phải chứng minh nguồn gốc cryptographic của package archive.
|
|
156
|
+
- Lock drift được phân loại hai nhánh, xem `## Lock drift` bên dưới.
|
|
156
157
|
- Optional runtime package được cài bằng exact version; installer đối chiếu registry integrity với digest đã phê duyệt trước khi cài.
|
|
157
158
|
- Khi cập nhật profile, lock được ghi trước; nếu profile không thể ghi thì lock được rollback. Process interruption tạo trạng thái mismatch fail-closed thay vì áp dụng profile thiếu lock.
|
|
158
159
|
|
|
@@ -166,6 +167,26 @@ Pack không thể cấp thêm quyền cho chính nó:
|
|
|
166
167
|
- `validate-action` chỉ kiểm tra contract, thời hạn và secret indicator; không authorize action, không đối chiếu requested permission với profile/lock và không đọc artifact để xác minh digest/size.
|
|
167
168
|
- External action vẫn cần human confirmation và executor riêng trước khi có side effect.
|
|
168
169
|
|
|
170
|
+
## Lock drift
|
|
171
|
+
|
|
172
|
+
Lock pin hai thứ khác nhau, và khi lệch thì câu trả lời cũng khác nhau.
|
|
173
|
+
|
|
174
|
+
**Consent — project phải đồng ý lại, session bị chặn (`blocked`):**
|
|
175
|
+
|
|
176
|
+
- `schemaVersion` của lock, `core.apiVersion`, `core.packageSource`;
|
|
177
|
+
- `profile` — `projectId`, `mode`, tên file, digest của document project tự lưu;
|
|
178
|
+
- `packs` — name, version, origin, source, owner, lifecycle và digest của manifest. Manifest khai luôn `provides`, nên artifact xuất hiện thêm, biến mất hoặc đổi path đều nằm ở nhánh này;
|
|
179
|
+
- permission theo hướng: granting key (`capabilities`, `filesystemRead`, `filesystemWrite`, `networkDomains`, `externalActions`) chặn khi **thêm**; restricting key (`protectedPaths`, `readOnlyPaths`, `shellProtectedPaths`) chặn khi **mất**.
|
|
180
|
+
|
|
181
|
+
**Build — platform đổi bản, lock được ghi lại và báo, session chạy tiếp (`repin`):**
|
|
182
|
+
|
|
183
|
+
- `core.packageVersion`, `core.runtimeFiles`, `core.packageDigest`;
|
|
184
|
+
- nội dung artifact (`artifacts[].digest`).
|
|
185
|
+
|
|
186
|
+
Vì sao nội dung artifact nằm ở nhánh build: adapter policy file, base policy, prompt, skill, subagent và recipe đều ship dạng artifact. Pin bytes của chúng nghĩa là bản sửa policy không bao giờ tới được project đang tham chiếu — đúng thứ mà việc ship policy tập trung tồn tại để làm. Không mất gì khi bỏ pin: danh sách artifact nằm trong manifest và manifest digest vẫn được pin ở `packs`, còn policy artifact bị nới lỏng vẫn chặn vì permission sau resolve được so trực tiếp theo hướng.
|
|
187
|
+
|
|
188
|
+
Việc pin build cũng chưa bao giờ chống được trường hợp nó trông giống: code kiểm tra lock chính là một trong các file mà lock pin, nên thứ gì sửa được platform đã cài thì sửa luôn được checker.
|
|
189
|
+
|
|
169
190
|
## Lifecycle
|
|
170
191
|
|
|
171
192
|
| Trạng thái | Ý nghĩa |
|
|
@@ -219,7 +240,7 @@ Thư mục nguồn phải chứa `packs/`, nằm trong project, và không chứ
|
|
|
219
240
|
piagent-capabilities vendor --profile .pi/piagent-profile.json
|
|
220
241
|
```
|
|
221
242
|
|
|
222
|
-
Lệnh này ghi vào `.pi/capability-vendor/<name>/` và in digest để review trước khi commit. Đây là chỗ duy nhất chạm network. Resolve và guard chỉ đọc thứ đã có trên đĩa, nên pack không đổi được giữa lúc review và lúc chạy. Nguồn khai mà chưa vendor thì resolve fail — không im lặng chạy với ít pack hơn profile yêu cầu.
|
|
243
|
+
Lệnh này ghi vào `.pi/capability-vendor/<name>/` và in digest để review trước khi commit. Đây là chỗ duy nhất trong đường capability chạm network — update check là đường riêng, xem `docs/security-threat-model.md`. Resolve và guard chỉ đọc thứ đã có trên đĩa, nên pack không đổi được giữa lúc review và lúc chạy. Nguồn khai mà chưa vendor thì resolve fail — không im lặng chạy với ít pack hơn profile yêu cầu.
|
|
223
244
|
|
|
224
245
|
### Vẫn deny by default
|
|
225
246
|
|
|
@@ -419,12 +419,13 @@ Các lệnh này chạy ngoài Pi.
|
|
|
419
419
|
| Command | Dùng khi nào |
|
|
420
420
|
|---|---|
|
|
421
421
|
| `npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0` | Cài Pi CLI tương thích với release hiện tại. |
|
|
422
|
-
| `npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
423
|
-
| `pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
422
|
+
| `npm install -g --ignore-scripts @piagent/platform@1.1.8` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
|
|
423
|
+
| `pi install git:github.com/Vt-mmm/piagent@v1.1.8` | Cài pinned release khi cần reproducible team setup. |
|
|
424
424
|
| `pi install git:github.com/Vt-mmm/piagent` | Cài latest package platform cho máy cá nhân/sandbox. |
|
|
425
425
|
| `piagent-update --check` | Báo version hiện tại vs version sẽ lên cho cả ba thành phần; không cài gì. |
|
|
426
426
|
| `piagent-update` | Full update một lệnh: Pi host → npm-global helper → Pi package, đúng thứ tự release yêu cầu. Thêm `--project <path>` để chạy doctor sau. |
|
|
427
427
|
| `piagent-update --version X.Y.Z` | Update tới đúng một release thay vì latest; dùng cho rollout theo lô hoặc rollback. |
|
|
428
|
+
| `PIAGENT_NO_UPDATE_CHECK=1` | Tắt notice "có bản mới" ở đầu session Pi. Mặc định bật, hỏi registry nhiều nhất một lần mỗi 24 giờ ở tiến trình nền. |
|
|
428
429
|
| Cài exact Pi host của release, rồi `npm install -g --ignore-scripts @piagent/platform@X.Y.Z` và `piagent-install --stable` | Sequence thủ công tương đương. Thứ tự bắt buộc: `piagent-install` fail nếu Pi host lệch bản pin và không tự cài host. Version pin của từng release nằm trong [release/install policy](release-install-policy.md). |
|
|
429
430
|
| Cài exact Pi host ghi trong release cũ, rồi helper `vPREVIOUS` và `piagent-install --stable` | Full rollback; đánh giá dependency risk của host cũ trước khi hạ version. |
|
|
430
431
|
| `piagent-install --stable --dry-run` | Preview Pi package matching với helper hiện tại; stable resolve tag → commit SHA. |
|
|
@@ -14,9 +14,9 @@ Repo này phải chạy được cho nhiều project/domain khác nhau. Vì vậ
|
|
|
14
14
|
|
|
15
15
|
| Use case | Package source |
|
|
16
16
|
|---|---|
|
|
17
|
-
| Team pinned release | `git:github.com/Vt-mmm/piagent@v1.1.
|
|
17
|
+
| Team pinned release | `git:github.com/Vt-mmm/piagent@v1.1.8` |
|
|
18
18
|
| Personal/sandbox dev | `git:github.com/Vt-mmm/piagent` |
|
|
19
|
-
| Registry distribution | `npm:@piagent/platform@1.1.
|
|
19
|
+
| Registry distribution | `npm:@piagent/platform@1.1.8` |
|
|
20
20
|
| Enterprise npm (fork under another scope) | `npm:@your-scope/platform@x.y.z` |
|
|
21
21
|
| Local platform dev | `/path/to/piagent` |
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@ Latest tiện cho máy cá nhân muốn nhận cập nhật nhanh. Pin tag/commi
|
|
|
26
26
|
|
|
27
27
|
| Channel | Command | Target | Policy |
|
|
28
28
|
|---|---|---|---|
|
|
29
|
-
| `stable` | `bash scripts/install-global.sh --stable` | Helper release tag resolved to commit SHA, currently `v1.1.
|
|
29
|
+
| `stable` | `bash scripts/install-global.sh --stable` | Helper release tag resolved to commit SHA, currently `v1.1.8` | Install the Pi package matching the helper. |
|
|
30
30
|
| `exact` | `bash scripts/install-global.sh --version vX.Y.Z --resolve-tag` | Requested tag resolved to commit SHA | Pi-package-only rollout, rollback, and incident recovery. |
|
|
31
31
|
| `dev` | `bash scripts/install-global.sh --dev` | Moving Git source | Personal/sandbox only; do not commit into project settings. |
|
|
32
32
|
| `local` | `bash scripts/install-global.sh --local` | Current checkout path | Platform development only. |
|
|
@@ -53,7 +53,7 @@ Root `package.json` có `pi` manifest trỏ tới:
|
|
|
53
53
|
Do đó team có thể:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
56
|
+
pi install git:github.com/Vt-mmm/piagent@v1.1.8
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Không cần biết internal folder `packages/piagent-core`. Source không pin chỉ dành cho personal/sandbox như bảng channel ở trên.
|
|
@@ -65,12 +65,12 @@ Team nên install global package một lần:
|
|
|
65
65
|
```bash
|
|
66
66
|
node --version # >= 22.19.0
|
|
67
67
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
68
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
68
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.8
|
|
69
69
|
piagent-install --stable --dry-run
|
|
70
70
|
piagent-install --stable
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
73
|
+
`pi install git:github.com/Vt-mmm/piagent@v1.1.8` vẫn hợp lệ nếu chỉ cần cài Pi package. Lệnh đó không tự tạo các binary terminal `piagent-*`; muốn có helper global thì dùng `npm install -g --ignore-scripts @piagent/platform@1.1.8`.
|
|
74
74
|
|
|
75
75
|
Support matrix hiện tại: macOS Apple Silicon + Bash và Linux x64 + Bash đã được verify; macOS Intel + Bash và Linux ARM64 + Bash là supported target cần chạy smoke trước rollout; native Windows chưa phải target rollout team; WSL2 experimental. Chi tiết và version runtime nằm trong [release/install policy](release-install-policy.md).
|
|
76
76
|
|
|
@@ -103,7 +103,7 @@ Nếu muốn commit sẵn `.pi/piagent-profile.json` vào repo hoặc bootstrap
|
|
|
103
103
|
```bash
|
|
104
104
|
bash scripts/setup.sh /path/to/project \
|
|
105
105
|
--profile be-readonly-fe \
|
|
106
|
-
--package-source git:github.com/Vt-mmm/piagent@v1.1.
|
|
106
|
+
--package-source git:github.com/Vt-mmm/piagent@v1.1.8 \
|
|
107
107
|
--mcp-preset core \
|
|
108
108
|
--subagents-preset safe
|
|
109
109
|
```
|
|
@@ -18,12 +18,12 @@ Support matrix release hiện tại: macOS Apple Silicon + Bash và Linux x64 +
|
|
|
18
18
|
```bash
|
|
19
19
|
node --version # >= 22.19.0
|
|
20
20
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
21
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
21
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.8
|
|
22
22
|
piagent-install --stable --dry-run
|
|
23
23
|
piagent-install --stable
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Khi seed `.pi/settings.json` cho team/repo cần audit lặp lại, giữ package source dạng pinned tag như `git:github.com/Vt-mmm/piagent@v1.1.
|
|
26
|
+
Khi seed `.pi/settings.json` cho team/repo cần audit lặp lại, giữ package source dạng pinned tag như `git:github.com/Vt-mmm/piagent@v1.1.8`. Máy cá nhân có thể dùng `git:github.com/Vt-mmm/piagent` để theo latest.
|
|
27
27
|
|
|
28
28
|
Nếu đang ở source checkout của platform, dùng helper theo channel để preview trước khi đổi:
|
|
29
29
|
|
|
@@ -99,7 +99,7 @@ install package once
|
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
101
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
102
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
102
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.8
|
|
103
103
|
piagent-install --stable --dry-run
|
|
104
104
|
piagent-install --stable
|
|
105
105
|
```
|
|
@@ -281,7 +281,7 @@ Không phải daily default. Dùng khi muốn tạo sẵn `.pi` files cho projec
|
|
|
281
281
|
bash /path/to/piagent/scripts/setup.sh /path/to/project \
|
|
282
282
|
--project-only \
|
|
283
283
|
--profile auto \
|
|
284
|
-
--package-source git:github.com/Vt-mmm/piagent@v1.1.
|
|
284
|
+
--package-source git:github.com/Vt-mmm/piagent@v1.1.8 \
|
|
285
285
|
--mcp-preset core \
|
|
286
286
|
--subagents-preset safe
|
|
287
287
|
```
|
|
@@ -854,8 +854,8 @@ Watchdog là optional adversarial reviewer ở cuối turn, không bật mặc
|
|
|
854
854
|
|
|
855
855
|
| Command | Dùng để |
|
|
856
856
|
|---|---|
|
|
857
|
-
| `npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
858
|
-
| `pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
857
|
+
| `npm install -g --ignore-scripts @piagent/platform@1.1.8` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
|
|
858
|
+
| `pi install git:github.com/Vt-mmm/piagent@v1.1.8` | Install pinned release cho reproducible team setup. |
|
|
859
859
|
| `pi install git:github.com/Vt-mmm/piagent` | Install latest platform package cho máy cá nhân/sandbox. |
|
|
860
860
|
| Cài exact Pi host của release, rồi `npm install -g --ignore-scripts @piagent/platform@X.Y.Z` và `piagent-install --stable` | Full update: đồng bộ host, terminal helper và Pi package. Mỗi release pin một Pi host chính xác; lấy đúng version của release đang cài trong [release/install policy](release-install-policy.md). |
|
|
861
861
|
| Cài exact Pi host ghi trong release cũ, rồi helper `vPREVIOUS` và `piagent-install --stable` | Full rollback; đánh giá lại dependency findings của host cũ trước khi hạ version. |
|
|
@@ -952,7 +952,7 @@ Mở lại Pi session sau khi install.
|
|
|
952
952
|
Cài lại terminal helper đúng release rồi kiểm tra `PATH`:
|
|
953
953
|
|
|
954
954
|
```bash
|
|
955
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
955
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.8
|
|
956
956
|
command -v piagent-install
|
|
957
957
|
```
|
|
958
958
|
|
package/docs/project-adapters.md
CHANGED
|
@@ -70,6 +70,29 @@ Path policy fields have distinct meanings:
|
|
|
70
70
|
|
|
71
71
|
Glob trong các field này neo từ project root, nên `backend/**` chỉ khớp backend nằm ở root. Với monorepo, phải liệt kê đúng vị trí thật (`packages/api/**`, `services/*/**`). Không dùng `**/api/**`: nó khớp luôn `packages/web/src/api/` — thư mục HTTP client của frontend — và biến chính phần được phép sửa thành read-only.
|
|
72
72
|
|
|
73
|
+
## Profile theo reference (`extends`)
|
|
74
|
+
|
|
75
|
+
Profile ở trên là dạng self-contained: project ghi toàn bộ policy. Dạng này đóng băng policy tại thời điểm onboard — platform sửa adapter thì project cũ vẫn giữ bản copy cũ mãi.
|
|
76
|
+
|
|
77
|
+
`extends` biến policy thành reference. Project chỉ giữ phần thuộc về project, phần còn lại lấy từ adapter trong platform đang cài:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"schemaVersion": 1,
|
|
82
|
+
"extends": "web-frontend",
|
|
83
|
+
"projectId": "my-project",
|
|
84
|
+
"displayName": "My Project"
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Quy tắc:
|
|
89
|
+
|
|
90
|
+
- `extends` trỏ tới một thư mục trong `adapters/` của platform đang cài. Tên không tồn tại → guard refuse, không fallback im lặng.
|
|
91
|
+
- Mọi key project tự khai **thay thế nguyên key** của adapter, không merge từng phần tử. Khai `protectedPaths` nghĩa là project sở hữu list đó và không nhận thay đổi từ adapter nữa.
|
|
92
|
+
- Không khai `extends` thì profile giữ nguyên hành vi cũ, self-contained.
|
|
93
|
+
|
|
94
|
+
`scripts/init-project.sh` và `/onboard-project` ghi dạng `extends` khi profile đến từ built-in adapter. Update platform sau đó không cần chạm vào project nào.
|
|
95
|
+
|
|
73
96
|
## Monorepo và workspace
|
|
74
97
|
|
|
75
98
|
Một project = một thư mục mở `pi`. Guard đọc profile tại `<cwd>/.pi/piagent-profile.json` và không đi ngược lên tìm, nên mở `pi` ở thư mục cha chứa nhiều project sẽ bỏ qua profile của từng project con và chạy unprofiled.
|
|
@@ -49,6 +49,8 @@ pi install /path/to/piagent
|
|
|
49
49
|
- `1.1.4`: nhận diện secret dạng CLI option (`--token`, `--password`, `--api-key`) và Authorization header ở mọi vị trí argument.
|
|
50
50
|
- `1.1.5`: thêm `piagent-update` — một lệnh update cả Pi host, terminal helper và Pi package đúng thứ tự.
|
|
51
51
|
- `1.1.6`: `be-readonly-fe` giữ được backend read-only trong monorepo, detect FE/BE đọc cả workspace, và rule detect gộp về một chỗ.
|
|
52
|
+
- `1.1.7`: update global không còn chặn project đang chạy — lock chỉ block khi grant thật sự đổi, và profile dạng `extends` nhận policy từ platform đang cài thay vì giữ bản copy lúc onboard.
|
|
53
|
+
- `1.1.8`: Pi báo ngay ở đầu session khi có release mới, kèm lệnh chạy update.
|
|
52
54
|
|
|
53
55
|
## Không publish
|
|
54
56
|
|
|
@@ -10,7 +10,7 @@ This file is the canonical install, update, rollback, and release checklist. Oth
|
|
|
10
10
|
|
|
11
11
|
All supported environments require Node.js `>=22.19.0` and Pi Coding Agent `0.82.0`. The Pi host is installed as a Node CLI; Pi Agent Platform still defines its own release matrix because the terminal helpers and shell policy rely on Bash/POSIX behavior.
|
|
12
12
|
|
|
13
|
-
| Surface | Status for v1.1.
|
|
13
|
+
| Surface | Status for v1.1.8 | Rollout guidance |
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| macOS Apple Silicon (`darwin/arm64`) + Bash | Verified for this release. | Safe default for team rollout after normal project smoke tests. |
|
|
16
16
|
| Linux x64 + Bash | Verified in GitHub Actions. | Safe default for CI/server usage after normal project smoke tests. |
|
|
@@ -33,7 +33,7 @@ These three components are versioned independently. A full install, update, or r
|
|
|
33
33
|
|
|
34
34
|
| Channel | Source shape | Mutability | Use when |
|
|
35
35
|
|---|---|---:|---|
|
|
36
|
-
| `stable` | `git:github.com/Vt-mmm/piagent@<resolved-commit-sha>` from `v1.1.
|
|
36
|
+
| `stable` | `git:github.com/Vt-mmm/piagent@<resolved-commit-sha>` from `v1.1.8` | Fixed after resolution | Default for team rollout. |
|
|
37
37
|
| `exact` | Tag, reviewed commit, or a tag resolved with `--resolve-tag` | Fixed when using a commit SHA | Pi-package-only roll forward, rollback, or reproduction. |
|
|
38
38
|
| `dev` | `git:github.com/Vt-mmm/piagent` | Moving | Personal machine or sandbox only. |
|
|
39
39
|
| `local` | `/path/to/piagent` | Local workspace | Platform development and dry-run validation. |
|
|
@@ -53,7 +53,7 @@ Use this flow when the team needs both terminal commands and the Pi package:
|
|
|
53
53
|
```bash
|
|
54
54
|
node --version # >= 22.19.0
|
|
55
55
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
56
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
56
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.8
|
|
57
57
|
piagent-install --stable --dry-run
|
|
58
58
|
piagent-install --stable
|
|
59
59
|
```
|
|
@@ -61,8 +61,8 @@ piagent-install --stable
|
|
|
61
61
|
The stable preview and apply output includes:
|
|
62
62
|
|
|
63
63
|
```text
|
|
64
|
-
currentRelease: v1.1.
|
|
65
|
-
tag: v1.1.
|
|
64
|
+
currentRelease: v1.1.8 (helper package version)
|
|
65
|
+
tag: v1.1.8
|
|
66
66
|
resolvedCommit: <40-char-sha>
|
|
67
67
|
source: git:github.com/Vt-mmm/piagent@<40-char-sha>
|
|
68
68
|
```
|
|
@@ -79,7 +79,7 @@ bash scripts/install-global.sh --stable
|
|
|
79
79
|
Use this only when terminal commands are not needed:
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
|
-
pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
82
|
+
pi install git:github.com/Vt-mmm/piagent@v1.1.8
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
Direct `pi install` does not create `piagent-*` commands on `PATH`.
|
|
@@ -21,7 +21,7 @@ This document covers the packaged guard, project profiles, capability locks, ins
|
|
|
21
21
|
- The operator, operating-system account, Pi host, and explicitly trusted project repository are trusted to execute code.
|
|
22
22
|
- Project-local Pi resources are loaded only after Pi project trust or an explicit operator override.
|
|
23
23
|
- A human remains available to approve destructive and external-provider actions.
|
|
24
|
-
- Supported release verification covers macOS Apple Silicon + Bash, Linux x64 + Bash, Node.js `>=22.19.0`, and the exact Pi host version declared by the release. macOS Intel + Bash and Linux ARM64 + Bash are supported targets that need local smoke verification before broad rollout. Native Windows is not a team-rollout target for v1.1.
|
|
24
|
+
- Supported release verification covers macOS Apple Silicon + Bash, Linux x64 + Bash, Node.js `>=22.19.0`, and the exact Pi host version declared by the release. macOS Intel + Bash and Linux ARM64 + Bash are supported targets that need local smoke verification before broad rollout. Native Windows is not a team-rollout target for v1.1.8, and WSL2 is experimental/unverified.
|
|
25
25
|
- Registry, GitHub, model-provider, MCP-provider, and Vercel controls are external dependencies. Their account security and platform guarantees are not replaced by this repository.
|
|
26
26
|
|
|
27
27
|
## Threat actors and failure modes
|
|
@@ -55,6 +55,16 @@ The final category is outside the containment capability of the Piagent guard. I
|
|
|
55
55
|
|
|
56
56
|
`read-only`, `workspace-write`, and `trusted-full-access` change the allowed working scope; they do not disable protected paths, secret redaction, capability integrity, destructive confirmation, or external-provider confirmation. `trusted-full-access` is intended for an already trusted repository in an externally safe environment. It is not a bypass or sandbox mode.
|
|
57
57
|
|
|
58
|
+
## Outbound network from a session
|
|
59
|
+
|
|
60
|
+
A Pi session makes one outbound request of its own: at most once every 24 hours it leaves a detached `npm view @piagent/platform version` behind to learn whether a newer release exists. This is the only network traffic the guard originates.
|
|
61
|
+
|
|
62
|
+
What it sends is an npm registry request for a public package and nothing else — no project path, no repository name, no profile, no telemetry. The registry reply reaches the terminal only if it matches `MAJOR.MINOR.PATCH` exactly; anything else is discarded rather than displayed, so a hostile or compromised registry response cannot become a line of attacker-chosen text in the operator's terminal. The answer is cached at `~/.pi/piagent-update-check.json`, an ordinary user-writable file whose worst case is suppressing the notice.
|
|
63
|
+
|
|
64
|
+
The session never waits on it: the request runs in a detached process with no inherited stdio, and the notice appears on the session after the one that refreshed the cache. A checkout is never told to update.
|
|
65
|
+
|
|
66
|
+
Set `PIAGENT_NO_UPDATE_CHECK=1` to switch it off. Nothing else changes.
|
|
67
|
+
|
|
58
68
|
## Security verification
|
|
59
69
|
|
|
60
70
|
Release gates include policy regression tests, TypeScript checking, runtime smoke tests, redaction benchmarks, package-content inspection, release-identity checks, installer tests, exact Pi host/add-on dependency audit, and CI on Linux x64 plus the current macOS runner. CodeQL covers JavaScript/TypeScript and GitHub Actions.
|
package/docs/team-onboarding.md
CHANGED
|
@@ -9,7 +9,7 @@ Một thành viên mới không cần biết local path của maintainer. Luồn
|
|
|
9
9
|
```bash
|
|
10
10
|
node --version # >= 22.19.0
|
|
11
11
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
12
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
12
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.8
|
|
13
13
|
piagent-install --stable
|
|
14
14
|
cd /path/to/project
|
|
15
15
|
pi
|
|
@@ -42,7 +42,7 @@ Mặc định team dùng stable helper:
|
|
|
42
42
|
```bash
|
|
43
43
|
node --version # >= 22.19.0
|
|
44
44
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
45
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
45
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.8
|
|
46
46
|
piagent-install --stable --dry-run
|
|
47
47
|
piagent-install --stable
|
|
48
48
|
```
|
|
@@ -70,7 +70,15 @@ piagent-update --check
|
|
|
70
70
|
piagent-update --project /path/to/project
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`--check` chỉ báo version, không đụng gì. Lên đúng một bản cụ thể thì `piagent-update --version 1.1.
|
|
73
|
+
`--check` chỉ báo version, không đụng gì. Lên đúng một bản cụ thể thì `piagent-update --version 1.1.8`.
|
|
74
|
+
|
|
75
|
+
Không cần ai nhớ đi kiểm tra: khi có release mới, Pi tự báo ngay ở dòng notice lúc mở session, kèm luôn lệnh chạy.
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
Piagent update available: 1.1.7 -> 1.1.8. Run `piagent-update` to move this machine to it.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Version mới nhất được hỏi npm registry nhiều nhất một lần mỗi 24 giờ, ở tiến trình nền tách rời, nên session không chờ gì cả — lần mở đầu tiên sau khi có bản mới có thể chưa thấy notice, lần sau sẽ thấy. Checkout của maintainer không bao giờ được báo. Tắt bằng `PIAGENT_NO_UPDATE_CHECK=1`; chi tiết ở `docs/security-threat-model.md`.
|
|
74
82
|
|
|
75
83
|
Sequence thủ công vẫn dùng được, và bắt buộc theo thứ tự này — `piagent-install` **fail** nếu Pi host lệch bản pin, nó không tự nâng host:
|
|
76
84
|
|
package/docs-site/index.html
CHANGED
|
@@ -798,7 +798,7 @@
|
|
|
798
798
|
<span>Pi Agent Platform</span>
|
|
799
799
|
</a>
|
|
800
800
|
<div class="top-actions">
|
|
801
|
-
<span class="pill"><span class="pill-dot"></span>v1.1.
|
|
801
|
+
<span class="pill"><span class="pill-dot"></span>v1.1.8 docs</span>
|
|
802
802
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
803
803
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
804
804
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -934,7 +934,7 @@
|
|
|
934
934
|
<article class="step">
|
|
935
935
|
<div>
|
|
936
936
|
<h3>Cài — hai lệnh</h3>
|
|
937
|
-
<p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.1.
|
|
937
|
+
<p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.1.8</span> đang pin. Chạy nó ngay trong thư mục project — nó cài Pi package, khởi tạo <span class="inline-code">.pi/</span>, cài MCP baseline cùng subagents, rồi chạy doctor. Thêm <span class="inline-code">--no-mcp</span>, <span class="inline-code">--no-subagents</span>, <span class="inline-code">--no-herdr</span> hoặc <span class="inline-code">--global-only</span> để bỏ bớt.</p>
|
|
938
938
|
<div class="table-wrap compact-table">
|
|
939
939
|
<table>
|
|
940
940
|
<thead>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piagent/platform",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
],
|
|
104
104
|
"scripts": {
|
|
105
105
|
"verify": "bash scripts/verify-local.sh",
|
|
106
|
-
"test": "node --test tests/*.test.mjs",
|
|
106
|
+
"test": "PIAGENT_NO_UPDATE_CHECK=1 node --test tests/*.test.mjs",
|
|
107
107
|
"test:policy": "node --test tests/policy-core.test.mjs",
|
|
108
108
|
"test:golden": "node --test tests/golden-enforcement.test.mjs",
|
|
109
109
|
"typecheck": "tsc --noEmit",
|
|
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { containsSensitiveText } from "../security/sensitive-data.js";
|
|
5
|
+
import { resolveProjectProfileDocument } from "./project-profile.js";
|
|
5
6
|
|
|
6
7
|
const API_VERSION = "piagent/v1";
|
|
7
8
|
const CORE_API_VERSION = 1;
|
|
@@ -785,6 +786,9 @@ function buildCoreIntegrity(root) {
|
|
|
785
786
|
// capability-sources.js decides which external trees become readable packs,
|
|
786
787
|
// so tampering with it changes which code the profile can grant.
|
|
787
788
|
"packages/piagent-core/capabilities/capability-sources.js",
|
|
789
|
+
// project-profile.js decides which document is enforced when a project takes
|
|
790
|
+
// its policy from an adapter, so it selects the protected paths themselves.
|
|
791
|
+
"packages/piagent-core/capabilities/project-profile.js",
|
|
788
792
|
"packages/piagent-core/security/sensitive-data.js",
|
|
789
793
|
"packages/piagent-core/extensions/piagent-guard.ts",
|
|
790
794
|
// guard-io.js loads the project profile and guard-shell-analysis.ts decides
|
|
@@ -799,7 +803,12 @@ function buildCoreIntegrity(root) {
|
|
|
799
803
|
// project-shape.js picks the profile a project is offered, and the profile is
|
|
800
804
|
// the document that carries protectedPaths and readOnlyPaths. Steering the
|
|
801
805
|
// recommendation is a way to choose what the guard will end up enforcing.
|
|
802
|
-
"packages/piagent-core/extensions/project-shape.js"
|
|
806
|
+
"packages/piagent-core/extensions/project-shape.js",
|
|
807
|
+
// update-check.js is the one file here that enforces nothing. It is pinned
|
|
808
|
+
// for a different reason: it starts a process on session start and puts a
|
|
809
|
+
// line in the operator's terminal, so tampering with it buys code execution
|
|
810
|
+
// and a message the operator has been trained to trust.
|
|
811
|
+
"packages/piagent-core/extensions/update-check.js"
|
|
803
812
|
];
|
|
804
813
|
return files.map((relativePath) => {
|
|
805
814
|
const resolved = resolveRegularFile(root, relativePath, MAX_ARTIFACT_BYTES);
|
|
@@ -904,7 +913,7 @@ export function resolveCapabilityProfileDocument(root, profile, options = {}) {
|
|
|
904
913
|
projectId: profile.projectId,
|
|
905
914
|
mode: profile.mode,
|
|
906
915
|
file: profileFile,
|
|
907
|
-
digest: sha256(stableJson(profile))
|
|
916
|
+
digest: sha256(stableJson(options.storedProfile ?? profile))
|
|
908
917
|
},
|
|
909
918
|
packs: [...selected.values()].map((pack) => ({
|
|
910
919
|
name: pack.name,
|
|
@@ -927,25 +936,118 @@ export function resolveCapabilityProfileDocument(root, profile, options = {}) {
|
|
|
927
936
|
|
|
928
937
|
export function resolveCapabilityProfile(root, profilePath, options = {}) {
|
|
929
938
|
const profileAbsolute = fs.realpathSync(profilePath);
|
|
930
|
-
const
|
|
939
|
+
const stored = readJsonFile(profileAbsolute);
|
|
940
|
+
const { profile } = resolveProjectProfileDocument(fs.realpathSync(root), stored);
|
|
931
941
|
return resolveCapabilityProfileDocument(root, profile, {
|
|
932
942
|
profileFile: path.basename(profileAbsolute),
|
|
943
|
+
// The lock records the document the project owns. A profile that takes its
|
|
944
|
+
// policy from an adapter is meant to follow that adapter, so digesting the
|
|
945
|
+
// resolved document instead would turn every platform correction into a
|
|
946
|
+
// lock the project has to re-agree to by hand.
|
|
947
|
+
storedProfile: stored,
|
|
933
948
|
packageSource: options.packageSource,
|
|
934
949
|
extraRoots: options.extraRoots
|
|
935
950
|
});
|
|
936
951
|
}
|
|
937
952
|
|
|
953
|
+
// A lock pins two different things and they deserve different answers when they
|
|
954
|
+
// stop matching.
|
|
955
|
+
//
|
|
956
|
+
// What the project consented to: which packs, from where, and how far they may
|
|
957
|
+
// reach. Granting more than that, or covering less than that, is a change the
|
|
958
|
+
// project has to agree to, so it blocks.
|
|
959
|
+
//
|
|
960
|
+
// Which build of the platform produced the resolution: its version, the digests
|
|
961
|
+
// of its runtime files, and the content of the artifacts its packs ship. That
|
|
962
|
+
// moves on every release. Blocking on it put a per-project chore behind a global
|
|
963
|
+
// update — every project on a profile that selects packs stopped working until
|
|
964
|
+
// its profile was reapplied — while not actually defending against the case it
|
|
965
|
+
// resembles: the code that verifies the lock is itself one of the files the lock
|
|
966
|
+
// pins, so anything able to rewrite the installed platform can rewrite the check
|
|
967
|
+
// along with it. A build move with an unchanged grant is re-pinned and reported,
|
|
968
|
+
// not refused.
|
|
969
|
+
//
|
|
970
|
+
// Artifact content sits on the build side deliberately. Adapter policy files,
|
|
971
|
+
// base policy, prompts, skills, subagents, and recipes all ship as artifacts, so
|
|
972
|
+
// pinning their bytes means a policy correction cannot reach a project that
|
|
973
|
+
// references it — which is the whole point of shipping policy centrally. Nothing
|
|
974
|
+
// is given up by that: which artifacts a pack provides is declared in its
|
|
975
|
+
// manifest, and the manifest digest is pinned in `packs`, so an artifact that
|
|
976
|
+
// appears, disappears, or moves still blocks. A policy artifact that weakens
|
|
977
|
+
// blocks too, because the permissions it resolves to are compared directly
|
|
978
|
+
// below.
|
|
979
|
+
const GRANTING_PERMISSIONS = ["capabilities", "filesystemRead", "filesystemWrite", "networkDomains", "externalActions"];
|
|
980
|
+
const RESTRICTING_PERMISSIONS = ["protectedPaths", "readOnlyPaths", "shellProtectedPaths"];
|
|
981
|
+
|
|
982
|
+
function consentedShape(document) {
|
|
983
|
+
return {
|
|
984
|
+
schemaVersion: document?.schemaVersion,
|
|
985
|
+
apiVersion: document?.core?.apiVersion,
|
|
986
|
+
packageSource: document?.core?.packageSource,
|
|
987
|
+
profile: document?.profile,
|
|
988
|
+
packs: document?.packs
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function consentDrift(expected, actual) {
|
|
993
|
+
const before = consentedShape(actual);
|
|
994
|
+
const after = consentedShape(expected);
|
|
995
|
+
return Object.keys(after)
|
|
996
|
+
.filter((key) => stableJson(after[key]) !== stableJson(before[key]))
|
|
997
|
+
.map((key) => `${key} no longer matches the locked value`);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function permissionDrift(expected, actual) {
|
|
1001
|
+
const locked = actual?.permissions ?? {};
|
|
1002
|
+
const resolved = expected?.permissions ?? {};
|
|
1003
|
+
const drift = [];
|
|
1004
|
+
|
|
1005
|
+
for (const key of GRANTING_PERMISSIONS) {
|
|
1006
|
+
const agreed = new Set(locked[key] ?? []);
|
|
1007
|
+
const added = (resolved[key] ?? []).filter((item) => !agreed.has(item));
|
|
1008
|
+
if (added.length > 0) drift.push(`${key} would grant ${added.join(", ")}`);
|
|
1009
|
+
}
|
|
1010
|
+
// A protection that disappears is a weakening even though the list shrank.
|
|
1011
|
+
for (const key of RESTRICTING_PERMISSIONS) {
|
|
1012
|
+
const kept = new Set(resolved[key] ?? []);
|
|
1013
|
+
const dropped = (locked[key] ?? []).filter((item) => !kept.has(item));
|
|
1014
|
+
if (dropped.length > 0) drift.push(`${key} would stop covering ${dropped.join(", ")}`);
|
|
1015
|
+
}
|
|
1016
|
+
return drift;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
export function classifyCapabilityLock(expected, actual) {
|
|
1020
|
+
const expectedDigest = sha256(stableJson(expected));
|
|
1021
|
+
const actualDigest = sha256(stableJson(actual));
|
|
1022
|
+
if (crypto.timingSafeEqual(Buffer.from(expectedDigest), Buffer.from(actualDigest))) {
|
|
1023
|
+
return { status: "current", reasons: [], expectedDigest, actualDigest };
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const reasons = [...consentDrift(expected, actual), ...permissionDrift(expected, actual)];
|
|
1027
|
+
if (reasons.length > 0) return { status: "blocked", reasons, expectedDigest, actualDigest };
|
|
1028
|
+
|
|
1029
|
+
return {
|
|
1030
|
+
status: "repin",
|
|
1031
|
+
reasons: [`platform build moved to ${expected?.core?.packageVersion ?? "an unknown version"}`],
|
|
1032
|
+
expectedDigest,
|
|
1033
|
+
actualDigest
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
938
1037
|
export function verifyCapabilityLock(root, profilePath, lockDocument, options = {}) {
|
|
939
1038
|
const expected = resolveCapabilityProfile(root, profilePath, {
|
|
940
1039
|
packageSource: options.packageSource,
|
|
941
1040
|
extraRoots: options.extraRoots
|
|
942
1041
|
});
|
|
943
|
-
const
|
|
944
|
-
const actualText = stableJson(lockDocument);
|
|
1042
|
+
const classification = classifyCapabilityLock(expected, lockDocument);
|
|
945
1043
|
return {
|
|
946
|
-
ok:
|
|
947
|
-
|
|
948
|
-
|
|
1044
|
+
// `ok` stays the question callers were already asking: may this project run?
|
|
1045
|
+
// A re-pinnable lock may, and the caller is told so through `status`.
|
|
1046
|
+
ok: classification.status !== "blocked",
|
|
1047
|
+
status: classification.status,
|
|
1048
|
+
reasons: classification.reasons,
|
|
1049
|
+
expectedDigest: classification.expectedDigest,
|
|
1050
|
+
actualDigest: classification.actualDigest,
|
|
949
1051
|
expected
|
|
950
1052
|
};
|
|
951
1053
|
}
|