feat(nono): establish dynamic permissions baseline
This commit is contained in:
@@ -24,9 +24,7 @@
|
||||
"globalSymlinks": {
|
||||
"dictate": "extensions/dictate",
|
||||
"effort-controls": "extensions/effort-controls",
|
||||
"pi-workflow": "extensions/pi-workflow",
|
||||
"prompt-snippets": "extensions/prompt-snippets",
|
||||
"update": "extensions/update"
|
||||
"prompt-snippets": "extensions/prompt-snippets"
|
||||
},
|
||||
"globalLocalPackages": {}
|
||||
}
|
||||
|
||||
Generated
+208
@@ -0,0 +1,208 @@
|
||||
{
|
||||
"version": 1,
|
||||
"policy": {
|
||||
"settingsAuthority": "$HOME/.pi/agent/settings.json",
|
||||
"linkAuthority": "config/pi-links.json",
|
||||
"activationRule": "Every selected source must be locked; any blocked entry prevents normal-profile activation."
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
"name": "@sovorn/pi-profile",
|
||||
"source": "npm:@sovorn/pi-profile",
|
||||
"installed": {
|
||||
"version": "0.6.0",
|
||||
"integrity": "sha512-S9hfncCw7qBDj2cz/HYC9q+P76GvqY2K8cmMGARDIN6fxvLxH8w8P8JQ2nFRm4rCNtadVGR9HAcaqC33TFNGFg=="
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The settings source omits an immutable version."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@smarzban/pi-editor",
|
||||
"source": "npm:@smarzban/pi-editor",
|
||||
"installed": {
|
||||
"version": "0.1.0",
|
||||
"integrity": "sha512-pewcNufszrGzOezewywVtPuDwexk3JyEcenGXLtvH7ewJ6n9bkzTs0LMjj9ZzwUQPrDQPNX06NolppXCKQ8vXw=="
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The settings source omits an immutable version."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pi-background-tasks",
|
||||
"source": "npm:pi-background-tasks@latest",
|
||||
"installed": {
|
||||
"version": "2.4.2",
|
||||
"integrity": "sha512-KDH2yv5yKnc2slUNMSsysVZleriuv8tbhe5L+AeplVAfijQsECN5YAWOz5TDbStCXLdJC15GaUQ1P87BXGk5Hg=="
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The settings source uses the floating latest tag."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pi-ask",
|
||||
"source": "ssh://git@git.jb.codes:7722/jb/pi-ask.git",
|
||||
"installed": {
|
||||
"revision": "80da3078281f839f075bd63bf221ddd0de2cbd86"
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The settings Git source omits an immutable revision."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "nolabs-ai/pi",
|
||||
"source": "/home/jb/.config/nono/packages/nolabs-ai/pi",
|
||||
"installed": {
|
||||
"version": "0.2.0",
|
||||
"registryRef": "refs/tags/pi-v0.2.0",
|
||||
"packageDigest": "2cfc2bd06b4ee1b90550e40e250609a97145933daac858554d9785b13710272e",
|
||||
"profileDigest": "26f87eca993933cd6d3d059d3d78a7545c488e6a5986224594f0694f40b9a764",
|
||||
"lockMetadata": {
|
||||
"path": "$HOME/.config/nono/packages/lockfile.json",
|
||||
"sha256": "131a7bc20698cc55130897f22027e0ec16bb423637d5d96b860374df9095a89a",
|
||||
"signerIdentity": "https://github.com/nolabs-ai/nono-packs/.github/workflows/publish-pi.yml@refs/tags/pi-v0.2.0",
|
||||
"repository": "nolabs-ai/nono-packs",
|
||||
"workflow": ".github/workflows/publish-pi.yml",
|
||||
"rekorLogIndex": 2305195909
|
||||
}
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The installed nono lock metadata records pinned=false."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@zhcsyncer/pi-glance",
|
||||
"source": "npm:@zhcsyncer/pi-glance",
|
||||
"installed": {
|
||||
"version": "0.8.0",
|
||||
"integrity": "sha512-BQbHYG4ZoNUAbiwivNPSggtCnyy9B2ziplmoCLE5ULVbz4RoHhvjso9IP54jWsWZuEqMMY0IoQISMWNLlM+THg=="
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The settings source omits an immutable version."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "@pi-kaush/pi-welcome-screen",
|
||||
"source": "npm:@pi-kaush/pi-welcome-screen@0.1.3",
|
||||
"installed": {
|
||||
"version": "0.1.3",
|
||||
"integrity": "sha512-RMEVEigGAt9SCK1+biHZ09IGOphfCTAf419YKA9PgKb09tCEGAXSfSeThmbXm+tPaJABlNuIv9NSIR8Md4NqSw=="
|
||||
},
|
||||
"status": "locked",
|
||||
"blockers": []
|
||||
},
|
||||
{
|
||||
"name": "pi-doppelclaude",
|
||||
"source": "npm:pi-doppelclaude",
|
||||
"installed": {
|
||||
"version": "0.10.0",
|
||||
"integrity": "sha512-RJAeLVgGTDB5ERUmswv4k7efrPIx/8v58vZR4z5YsWQDZiBvPPsrkhk6rj7OzmNtOtEJCQoHZYWnYeSm/MOkjw=="
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The settings source omits an immutable version."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "diagram-design",
|
||||
"source": "https://github.com/cathrynlavery/diagram-design",
|
||||
"installed": {
|
||||
"revision": "4faae6696c2953b59dee2b89ad89c688f80c3a67"
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The settings Git source omits an immutable revision."
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensions": [
|
||||
{
|
||||
"name": "dictate",
|
||||
"source": "extensions/dictate",
|
||||
"installed": {
|
||||
"repository": "/srv/data/code/harness-tools",
|
||||
"revision": "aa50f0fd1665b0da443d002e049d7b5dcf65b3bb"
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The managed link targets a mutable repository checkout rather than an immutable installed snapshot."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "effort-controls",
|
||||
"source": "extensions/effort-controls",
|
||||
"installed": {
|
||||
"repository": "/srv/data/code/harness-tools",
|
||||
"revision": "aa50f0fd1665b0da443d002e049d7b5dcf65b3bb"
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The managed link targets a mutable repository checkout rather than an immutable installed snapshot."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "prompt-snippets",
|
||||
"source": "extensions/prompt-snippets",
|
||||
"installed": {
|
||||
"repository": "/srv/data/code/harness-tools",
|
||||
"revision": "aa50f0fd1665b0da443d002e049d7b5dcf65b3bb"
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The managed link targets a mutable repository checkout rather than an immutable installed snapshot."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pi-interactive-subagents",
|
||||
"source": "/srv/data/repo-store/pi-interactive-subagents",
|
||||
"installed": {
|
||||
"revision": "4105b03981460944657ec6ac81ff93daeb0cad6c"
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The active unmanaged link targets a mutable repository checkout and is absent from config/pi-links.json."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pi-todo-herdr",
|
||||
"source": "/srv/data/repo-store/pi-todo-herdr",
|
||||
"installed": {
|
||||
"revision": "fbe8baf18cf4b875754607d55a475266c994fa7b"
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The active unmanaged link targets a mutable repository checkout and is absent from config/pi-links.json."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "lm-studio",
|
||||
"source": "$HOME/.pi/agent/extensions/lm-studio.ts",
|
||||
"installed": {
|
||||
"sha256": "474d1288cb7c528e44b14fcc498ae9f4be04fcbb248dd22d76adb13c2f8c134e"
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The active extension is an unmanaged regular file with no immutable source revision.",
|
||||
"Slice 1 must import the reviewed implementation and quarantine the unmanaged source before activation."
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "update",
|
||||
"source": "extensions/update",
|
||||
"installed": {
|
||||
"repository": "/srv/data/code/harness-tools",
|
||||
"revision": "aa50f0fd1665b0da443d002e049d7b5dcf65b3bb"
|
||||
},
|
||||
"status": "blocked",
|
||||
"blockers": [
|
||||
"The extension remains linked in the active Pi state but is intentionally absent from normal config/pi-links.json exposure."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
# Nono profile and Pi package baseline
|
||||
|
||||
Captured for Slice 0 on 27AUG2026 with nono 0.74.0. This record contains no credentials or secret values.
|
||||
|
||||
## Authority and activation rule
|
||||
|
||||
- `profiles/nono/*.json` is the reviewed profile source.
|
||||
- `config/pi-package-lock.json` records observed immutable installed evidence and blockers. It does not select packages.
|
||||
- `$HOME/.pi/agent/settings.json` selects active Pi packages.
|
||||
- `config/pi-links.json` owns repository-managed resource links. It is not package-source authority.
|
||||
- Any package-lock entry with `status: blocked` prevents normal-profile activation until maintenance resolves the blocker and refreshes the evidence.
|
||||
- Profile inheritance is additive. A child profile cannot be treated as narrowing a broader inherited grant.
|
||||
|
||||
## Active profile state
|
||||
|
||||
| Profile | Active form at capture | Repository disposition |
|
||||
| --- | --- | --- |
|
||||
| `pi-local` | Symlink to `profiles/nono/pi-local.json` | Already repository-managed. |
|
||||
| `pi-work` | Symlink to `profiles/nono/pi-work.json` | Already repository-managed. |
|
||||
| `pi-orca-local` | Regular file | Approved active drift imported into Git; host relink remains pending. |
|
||||
| `pi-harness-tools` | Regular file | Approved active drift imported into Git; host relink remains pending. |
|
||||
|
||||
The imported `pi-orca-local` drift is limited to exact read-only access for `$HOME/.pi/agent/AGENTS.md`. Its saved grant for `$HOME/.pi/agent/extensions/pi-workflow/index.ts` was not imported because the user removed that extension and deferred its replacement to a future skill. The imported `pi-harness-tools` drift is limited to exact read-only access for `$HOME/.pi/agent/extensions/pi-todo-herdr/index.ts` and `$HOME/.config/herdr/herdr.sock`. Its inherited `pi-local` policy already contains the Herdr Unix-socket grant. The active files used read/write `allow_file` saved grants; the repository deliberately narrows them to `read_file` because normal sessions do not need to modify trusted instructions or extension code. No other active saved grant was imported.
|
||||
|
||||
The `pi-workflow` extension was unlinked from active Pi state by the user during this slice. It is also absent from normal `config/pi-links.json` exposure so reconciliation will not recreate it. The future replacement is tracked in `docs/todos.md`.
|
||||
|
||||
## Draft disposition
|
||||
|
||||
| Draft | SHA-256 | Evidence | Approved disposition |
|
||||
| --- | --- | --- | --- |
|
||||
| `pi-local-dictate.json` | `4ec20ea2b68f9eaed46e0ae1e3fb86f3e3ee32d9c3b7f9dc3bc0cc17a8f27e88` | Filename and internal name disagree: the internal name is `pi-local`. Its dictation-related state and socket grants are represented by reviewed repository policy. | Permanently delete after this evidence is committed. |
|
||||
| `pi-harness-tools.json` | `79ba451192ced087a25c17c8d006f8010374adbad7685f6ac6357e838abbb6b8` | Internal name matches. Its approved saved grants are imported above. | Permanently delete after this evidence is committed. |
|
||||
|
||||
Host-only cleanup, after verifying both hashes:
|
||||
|
||||
```bash
|
||||
printf '%s %s\n' \
|
||||
4ec20ea2b68f9eaed46e0ae1e3fb86f3e3ee32d9c3b7f9dc3bc0cc17a8f27e88 "$HOME/.config/nono/profile-drafts/pi-local-dictate.json" \
|
||||
79ba451192ced087a25c17c8d006f8010374adbad7685f6ac6357e838abbb6b8 "$HOME/.config/nono/profile-drafts/pi-harness-tools.json" | sha256sum --check --strict && \
|
||||
rm -- "$HOME/.config/nono/profile-drafts/pi-local-dictate.json" \
|
||||
"$HOME/.config/nono/profile-drafts/pi-harness-tools.json"
|
||||
```
|
||||
|
||||
The `.nono-pi-pack-marker` is package wiring, not a profile draft, and remains in place.
|
||||
|
||||
## Package and extension inventory
|
||||
|
||||
`config/pi-package-lock.json` captures all nine active package selections:
|
||||
|
||||
- Six npm packages with exact installed versions and npm integrity values.
|
||||
- `pi-ask` and `diagram-design` with exact installed Git commits.
|
||||
- `nolabs-ai/pi` 0.2.0 with signed registry provenance and installed artifact digests.
|
||||
|
||||
Only `@pi-kaush/pi-welcome-screen@0.1.3` is currently locked end to end. Bare npm sources, `@latest`, and Git URLs without revisions remain blocked even though their installed contents are identified. `nolabs-ai/pi` remains blocked because the nono lockfile records `pinned: false`.
|
||||
|
||||
The extension inventory records the active repository-backed links and their observed commits. Mutable checkout targets and unmanaged links remain blocked until maintenance installs immutable snapshots. The unmanaged active `lm-studio.ts` file is recorded by SHA-256 and remains blocked pending the Slice 1 import and quarantine. `update` remains actively linked at capture time but has been removed from normal link exposure. The active `pi-workflow` link was removed by the user and is not part of the current active inventory.
|
||||
|
||||
## Maintenance-only follow-up
|
||||
|
||||
No following command was run as part of repository implementation or automated verification.
|
||||
|
||||
1. Review `config/pi-package-lock.json`, replace floating package selectors with immutable selectors, pin and re-verify `nolabs-ai/pi`, and install immutable extension snapshots.
|
||||
2. Validate the repository profiles and review their resolved policies.
|
||||
3. Relink the two regular active profiles to reviewed repository sources:
|
||||
|
||||
```bash
|
||||
ln -sfn /srv/data/code/harness-tools/profiles/nono/pi-orca-local.json \
|
||||
"$HOME/.config/nono/profiles/pi-orca-local.json"
|
||||
ln -sfn /srv/data/code/harness-tools/profiles/nono/pi-harness-tools.json \
|
||||
"$HOME/.config/nono/profiles/pi-harness-tools.json"
|
||||
nono profile validate pi-orca-local --strict
|
||||
nono profile validate pi-harness-tools --strict
|
||||
```
|
||||
|
||||
4. From the integrated primary `/srv/data/code/harness-tools` checkout, review the dry-run and reconcile links from tracked sources. This will remove stale repository-owned `update` exposure while preserving unrelated external links. The current tool treats primary-checkout links as conflicts when invoked from a linked worktree; that limitation is tracked in `docs/todos.md`.
|
||||
|
||||
```bash
|
||||
cd /srv/data/code/harness-tools
|
||||
node scripts/pi-links.mjs prune --dry-run
|
||||
node scripts/pi-links.mjs prune
|
||||
node scripts/pi-links.mjs check
|
||||
```
|
||||
|
||||
5. Run the hash-guarded draft cleanup command above.
|
||||
|
||||
## Restoration and rollback
|
||||
|
||||
Before host relinking, the active regular profile files remain the immediate rollback source. After relinking, Git restores the reviewed definitions:
|
||||
|
||||
```bash
|
||||
git restore --source=<reviewed-commit> -- \
|
||||
profiles/nono/pi-orca-local.json \
|
||||
profiles/nono/pi-harness-tools.json && \
|
||||
nono profile validate --strict profiles/nono/pi-orca-local.json && \
|
||||
nono profile validate --strict profiles/nono/pi-harness-tools.json
|
||||
```
|
||||
|
||||
Managed links are restored only through `config/pi-links.json` and `scripts/pi-links.mjs`; generated destinations are never edited directly. Draft deletion is intentional and irreversible: the recorded hashes prove identity but cannot reconstruct prior bytes. No retained artifact is claimed as a byte-for-byte backup, and deleted drafts must not be placed back in nono's draft search path.
|
||||
|
||||
## Actions not performed
|
||||
|
||||
This slice did not activate or relink profiles, change Pi settings, credentials, or provider configuration, install or update packages, pin the nono package, reconcile or prune generated links, broaden production access, or edit generated skill links. External draft deletion is a separately recorded host-only cleanup step and must not be inferred from the repository changes.
|
||||
@@ -1,5 +1,13 @@
|
||||
# Todos
|
||||
|
||||
## Make `pi-links` checks work from linked worktrees
|
||||
|
||||
A Slice 0 `node scripts/pi-links.mjs prune --dry-run` from `.worktrees/nono-dynamic-permissions-slice-zero-direct` exited 2 and classified every active link into the primary checkout as an outside-repository conflict. The tool anchors ownership to the current worktree root, while active links correctly target the primary repository checkout. Teach check and dry-run planning to recognize canonical checkouts sharing the same Git common directory without broadening stale-link deletion to unrelated repositories. Add linked-worktree fixtures before changing prune behavior. Until fixed, run host reconciliation only from the integrated primary checkout.
|
||||
|
||||
## Replace `pi-workflow` with a skill-based orchestrator
|
||||
|
||||
The strict `pi-workflow` extension repeatedly failed before implementation because planner output and persisted state-machine constraints were too brittle for adaptive subagent orchestration. In a separate session, design a repository-managed skill where the main Pi agent owns the plan, task tree, isolated worktree, explicit user approval gates, verification, and independent review while launching standard `scout` and `worker` subagents directly. Preserve file-ownership boundaries, no inferred approvals, review independence, and resumable repository evidence, but prefer native Pi primitives over a custom orchestration engine. Evaluate a small committed state manifest for crash recovery before retiring or archiving the extension. Use the abandoned `.worktrees/pi-workflow-plan-seed` implementation only as reference; do not integrate it by default.
|
||||
|
||||
## Support subagent launches through `multi_tool_use.parallel`
|
||||
|
||||
A user reported that launching the repository's subagent plugin through Pi's `multi_tool_use.parallel` wrapper does not work, although equivalent launches succeed outside the wrapper in another session. Reproduce with two minimal independent subagent launches, comparing direct tool calls with wrapped calls. Capture whether failure occurs during schema validation, dispatch, asynchronous acknowledgement, task registration, or completion delivery. The wrapper must return promptly for fire-and-forget launches, preserve each subagent's name and working directory, and allow the existing automatic completion notification to deliver each result exactly once without polling. Add focused integration coverage for successful parallel launch, one-child failure, cancellation, and duplicate-delivery prevention before changing either tool.
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
],
|
||||
"read": [
|
||||
"$HOME/.config/orca/linux-orca-cli-shim"
|
||||
],
|
||||
"read_file": [
|
||||
"$HOME/.config/herdr/herdr.sock",
|
||||
"$HOME/.pi/agent/extensions/pi-todo-herdr/index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
},
|
||||
"filesystem": {
|
||||
"read_file": [
|
||||
"$HOME/.pi/agent/AGENTS.md",
|
||||
"/home/jb/.config/orca/agent-hooks/endpoint.env"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -10,8 +10,14 @@ import {
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { buildPlan, run } from "./pi-links.mjs";
|
||||
|
||||
const repositoryRoot = path.resolve(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
"..",
|
||||
);
|
||||
|
||||
async function writeJson(file, value) {
|
||||
await mkdir(path.dirname(file), { recursive: true });
|
||||
await writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
|
||||
@@ -205,6 +211,62 @@ test("prune removes only stale links whose targets are inside the repository", a
|
||||
assert.equal(await readlink(external), path.join(options.root, "outside"));
|
||||
});
|
||||
|
||||
test("removed update exposure is prunable without touching unrelated extension links", async () => {
|
||||
const options = await fixture();
|
||||
const updateSource = path.join(options.repoRoot, "extensions", "update");
|
||||
const externalSource = path.join(options.root, "external-update");
|
||||
const extensionsDir = path.join(options.agentDir, "extensions");
|
||||
const updateLink = path.join(extensionsDir, "update");
|
||||
const externalLink = path.join(extensionsDir, "external");
|
||||
await writeExtension(updateSource);
|
||||
await writeExtension(externalSource);
|
||||
await mkdir(extensionsDir, { recursive: true });
|
||||
await symlink(updateSource, updateLink);
|
||||
await symlink(externalSource, externalLink);
|
||||
|
||||
const plan = await buildPlan(options);
|
||||
assert.ok(
|
||||
plan.operations.some(
|
||||
(operation) =>
|
||||
operation.type === "remove-link" &&
|
||||
operation.destination === updateLink,
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
!plan.operations.some(
|
||||
(operation) =>
|
||||
operation.type === "remove-link" &&
|
||||
operation.destination === externalLink,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test("normal exposure and package evidence retain the Slice 0 security boundary", async () => {
|
||||
const links = JSON.parse(
|
||||
await readFile(path.join(repositoryRoot, "config", "pi-links.json"), "utf8"),
|
||||
);
|
||||
assert.equal(links.extensions.globalSymlinks.update, undefined);
|
||||
assert.equal(links.extensions.globalSymlinks["pi-workflow"], undefined);
|
||||
|
||||
const lock = JSON.parse(
|
||||
await readFile(
|
||||
path.join(repositoryRoot, "config", "pi-package-lock.json"),
|
||||
"utf8",
|
||||
),
|
||||
);
|
||||
assert.equal(lock.packages.length, 9);
|
||||
assert.ok(
|
||||
lock.packages.every((entry) =>
|
||||
["locked", "blocked"].includes(entry.status),
|
||||
),
|
||||
);
|
||||
assert.ok(lock.extensions.some((entry) => entry.name === "lm-studio"));
|
||||
assert.equal(
|
||||
lock.extensions.find((entry) => entry.name === "update")?.status,
|
||||
"blocked",
|
||||
);
|
||||
});
|
||||
|
||||
test("duplicate declared skill names fail before planning changes", async () => {
|
||||
const options = await fixture();
|
||||
await writeSkill(
|
||||
|
||||
Reference in New Issue
Block a user