@beremaran/ralphie 0.2.2 → 0.2.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 +10 -0
- package/dist/ralphie.js +21 -80
- package/docs/architecture.md +8 -6
- package/docs/configuration.md +2 -1
- package/docs/development.md +10 -6
- package/docs/getting-started.md +3 -4
- package/docs/safety.md +36 -40
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ All notable changes to Ralphie are documented here. The project follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.2.3] - 2026-10-08
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Agent sessions now inherit Ralphie's GitHub and git authority and may use
|
|
13
|
+
`gh` to read issues, pull requests, comments and CI results. See
|
|
14
|
+
`docs/safety.md#session-authority`.
|
|
15
|
+
- `bun run smoke:live` now exits with status 2 unless an explicit opt-in
|
|
16
|
+
environment variable is set, because live runs spend model quota.
|
|
17
|
+
|
|
8
18
|
## [0.2.2] - 2026-10-07
|
|
9
19
|
|
|
10
20
|
### Changed
|
package/dist/ralphie.js
CHANGED
|
@@ -26490,10 +26490,6 @@ ${clone2.stderr}` : "";
|
|
|
26490
26490
|
};
|
|
26491
26491
|
var runGit = (runner, repositoryPath, args, signal) => runner.run("git", ["-C", repositoryPath, ...args], signal === undefined ? undefined : { signal });
|
|
26492
26492
|
var requireGit = async (runner, repositoryPath, args, failureMessage, signal) => await requireSuccess(runner, "git", ["-C", repositoryPath, ...args], failureMessage, signal === undefined ? undefined : { signal });
|
|
26493
|
-
var DISABLED_PUSH_URL = "disabled://ralphie-sessions-cannot-push";
|
|
26494
|
-
var disablePush = async (runner, repositoryPath, signal) => {
|
|
26495
|
-
await requireGit(runner, repositoryPath, ["remote", "set-url", "--push", "origin", DISABLED_PUSH_URL], "Failed to disable pushing from the workspace.", signal);
|
|
26496
|
-
};
|
|
26497
26493
|
var selectBranch = async (runner, repositoryPath, branch, signal) => {
|
|
26498
26494
|
if (branch !== undefined)
|
|
26499
26495
|
return branch;
|
|
@@ -26520,7 +26516,6 @@ var prepareRepositoryState = async (runner, parsed, repositoryPath, branch, exis
|
|
|
26520
26516
|
if (originSlug.toLowerCase() !== parsed.slug.toLowerCase()) {
|
|
26521
26517
|
throw new Error(`${repositoryPath} contains ${originSlug}, not ${parsed.slug}.`);
|
|
26522
26518
|
}
|
|
26523
|
-
await disablePush(runner, repositoryPath, signal);
|
|
26524
26519
|
if (exists)
|
|
26525
26520
|
await requireGit(runner, repositoryPath, ["fetch", "--prune", "origin"], `Failed to fetch ${parsed.slug}.`, signal);
|
|
26526
26521
|
const status = await requireGit(runner, repositoryPath, ["status", "--porcelain"], `Failed to inspect ${parsed.slug}.`, signal);
|
|
@@ -27922,9 +27917,10 @@ var TRACKER_DOC = `# Issue tracker
|
|
|
27922
27917
|
|
|
27923
27918
|
Ralphie supplies the issue content in the prompt. Work from that text.
|
|
27924
27919
|
|
|
27925
|
-
|
|
27926
|
-
|
|
27927
|
-
change to the tracker itself.
|
|
27920
|
+
You may use the \`gh\` CLI to read issues, pull requests, comments and CI results.
|
|
27921
|
+
Do not create, edit, comment on, label, close or reopen anything.
|
|
27922
|
+
Ralphie performs every change to the tracker itself.
|
|
27923
|
+
For raw GitHub API calls, pass \`--method GET\`; never use the API to write.
|
|
27928
27924
|
`;
|
|
27929
27925
|
var labelsDoc = (labels) => [
|
|
27930
27926
|
"# Triage labels",
|
|
@@ -31444,48 +31440,7 @@ var nodeSkillFileSystem = {
|
|
|
31444
31440
|
}
|
|
31445
31441
|
};
|
|
31446
31442
|
|
|
31447
|
-
// src/harness/app/session-
|
|
31448
|
-
var GITHUB_CREDENTIAL_VARIABLES = [
|
|
31449
|
-
"GH_TOKEN",
|
|
31450
|
-
"GITHUB_TOKEN",
|
|
31451
|
-
"GH_ENTERPRISE_TOKEN",
|
|
31452
|
-
"GITHUB_ENTERPRISE_TOKEN"
|
|
31453
|
-
];
|
|
31454
|
-
var SSH_AGENT_VARIABLES = [
|
|
31455
|
-
"SSH_AUTH_SOCK",
|
|
31456
|
-
"SSH_ASKPASS",
|
|
31457
|
-
"GIT_ASKPASS"
|
|
31458
|
-
];
|
|
31459
|
-
var isolatedEnvironment = (env, configDirectory) => ({
|
|
31460
|
-
...env,
|
|
31461
|
-
...Object.fromEntries([...GITHUB_CREDENTIAL_VARIABLES, ...SSH_AGENT_VARIABLES].map((name) => [
|
|
31462
|
-
name,
|
|
31463
|
-
undefined
|
|
31464
|
-
])),
|
|
31465
|
-
GH_CONFIG_DIR: configDirectory,
|
|
31466
|
-
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
31467
|
-
GIT_CONFIG_SYSTEM: "/dev/null",
|
|
31468
|
-
GIT_CONFIG_NOSYSTEM: "1",
|
|
31469
|
-
GIT_CONFIG_COUNT: "1",
|
|
31470
|
-
GIT_CONFIG_KEY_0: "credential.helper",
|
|
31471
|
-
GIT_CONFIG_VALUE_0: "",
|
|
31472
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
31473
|
-
GIT_SSH_COMMAND: "false"
|
|
31474
|
-
});
|
|
31475
|
-
var isolateSessions = (inner, scratch) => {
|
|
31476
|
-
const run = async (request2) => {
|
|
31477
|
-
const directory = await scratch.create();
|
|
31478
|
-
try {
|
|
31479
|
-
return await inner.run({
|
|
31480
|
-
...request2,
|
|
31481
|
-
env: isolatedEnvironment(request2.env, directory.path)
|
|
31482
|
-
});
|
|
31483
|
-
} finally {
|
|
31484
|
-
await directory.remove().catch(() => {});
|
|
31485
|
-
}
|
|
31486
|
-
};
|
|
31487
|
-
return { run };
|
|
31488
|
-
};
|
|
31443
|
+
// src/harness/app/read-only-session-guard.ts
|
|
31489
31444
|
var guardReadOnlySessions = (inner, fingerprint) => {
|
|
31490
31445
|
const run = async (request2) => {
|
|
31491
31446
|
if (request2.access !== "read-only")
|
|
@@ -31509,20 +31464,6 @@ var guardReadOnlySessions = (inner, fingerprint) => {
|
|
|
31509
31464
|
return { run };
|
|
31510
31465
|
};
|
|
31511
31466
|
|
|
31512
|
-
// src/harness/adapters/scratch-directory.ts
|
|
31513
|
-
import { mkdtemp as mkdtemp2, rm as rm6 } from "fs/promises";
|
|
31514
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
31515
|
-
import { join as join7 } from "path";
|
|
31516
|
-
var makeTemporaryScratchDirectories = () => ({
|
|
31517
|
-
create: async () => {
|
|
31518
|
-
const path = await mkdtemp2(join7(tmpdir2(), "ralphie-session-"));
|
|
31519
|
-
return {
|
|
31520
|
-
path,
|
|
31521
|
-
remove: () => rm6(path, { recursive: true, force: true })
|
|
31522
|
-
};
|
|
31523
|
-
}
|
|
31524
|
-
});
|
|
31525
|
-
|
|
31526
31467
|
// src/git/adapters/working-tree.ts
|
|
31527
31468
|
import { createHash as createHash2 } from "crypto";
|
|
31528
31469
|
var makeGitWorkingTreeService = (runner) => ({
|
|
@@ -31616,7 +31557,7 @@ var provideRepositoryFacts = (inner, read) => {
|
|
|
31616
31557
|
};
|
|
31617
31558
|
|
|
31618
31559
|
// src/run/adapters/state.ts
|
|
31619
|
-
import { mkdir as mkdir7, rename as rename4, rm as
|
|
31560
|
+
import { mkdir as mkdir7, rename as rename4, rm as rm6, writeFile as writeFile7 } from "fs/promises";
|
|
31620
31561
|
import { dirname as dirname4 } from "path";
|
|
31621
31562
|
|
|
31622
31563
|
// src/run/state.ts
|
|
@@ -31765,7 +31706,7 @@ var persistRunStateAtomically = async (path, state) => {
|
|
|
31765
31706
|
});
|
|
31766
31707
|
await rename4(temporaryPath, path);
|
|
31767
31708
|
} catch (cause) {
|
|
31768
|
-
await
|
|
31709
|
+
await rm6(temporaryPath, { force: true }).catch(() => {
|
|
31769
31710
|
return;
|
|
31770
31711
|
});
|
|
31771
31712
|
throw new RalphieError({
|
|
@@ -31798,7 +31739,7 @@ var makeIdGenerator = () => ({
|
|
|
31798
31739
|
});
|
|
31799
31740
|
|
|
31800
31741
|
// src/workspace/adapters/workspace.ts
|
|
31801
|
-
import { mkdir as mkdir8, rm as
|
|
31742
|
+
import { mkdir as mkdir8, rm as rm7 } from "fs/promises";
|
|
31802
31743
|
import { homedir as homedir2 } from "os";
|
|
31803
31744
|
import { parse as parse7, resolve as resolve2, sep as sep2 } from "path";
|
|
31804
31745
|
var assertSafeCleanupTarget = (workspace) => {
|
|
@@ -31830,7 +31771,7 @@ var WorkspaceLive = {
|
|
|
31830
31771
|
},
|
|
31831
31772
|
remove: async (workspace) => {
|
|
31832
31773
|
try {
|
|
31833
|
-
await
|
|
31774
|
+
await rm7(assertSafeCleanupTarget(workspace), {
|
|
31834
31775
|
recursive: true,
|
|
31835
31776
|
force: true
|
|
31836
31777
|
});
|
|
@@ -31890,7 +31831,7 @@ var makeLiveRuntime = ({
|
|
|
31890
31831
|
})
|
|
31891
31832
|
}
|
|
31892
31833
|
});
|
|
31893
|
-
const harness = provideRepositoryFacts(guardReadOnlySessions(
|
|
31834
|
+
const harness = provideRepositoryFacts(guardReadOnlySessions(bareHarness, makeGitWorkingTreeService(commandRunner).fingerprint), makeGitRepositoryFactsService(commandRunner).read);
|
|
31894
31835
|
const gitRepository = makeGitRepositoryService(commandRunner);
|
|
31895
31836
|
const gitRepositoryInvariant = makeGitRepositoryInvariantService(commandRunner);
|
|
31896
31837
|
const gitIssueCheckpoint = makeGitIssueCheckpointService(commandRunner);
|
|
@@ -33223,7 +33164,7 @@ var issueWorkflow = { run: workflow };
|
|
|
33223
33164
|
// package.json
|
|
33224
33165
|
var package_default = {
|
|
33225
33166
|
name: "@beremaran/ralphie",
|
|
33226
|
-
version: "0.2.
|
|
33167
|
+
version: "0.2.3",
|
|
33227
33168
|
description: "Turn a GitHub issue queue into reviewed commits with coding-agent harnesses.",
|
|
33228
33169
|
module: "./dist/ralphie.js",
|
|
33229
33170
|
main: "./dist/ralphie.js",
|
|
@@ -33310,7 +33251,7 @@ var package_default = {
|
|
|
33310
33251
|
|
|
33311
33252
|
// src/build-info.ts
|
|
33312
33253
|
var LOCAL_BUILD_COMMIT_SHA = "local";
|
|
33313
|
-
var injectedBuildInfo = { version: "0.2.
|
|
33254
|
+
var injectedBuildInfo = { version: "0.2.3", commitSha: "local" };
|
|
33314
33255
|
var BUILD_INFO = Object.freeze(injectedBuildInfo ?? {
|
|
33315
33256
|
version: package_default.version,
|
|
33316
33257
|
commitSha: LOCAL_BUILD_COMMIT_SHA
|
|
@@ -33336,21 +33277,21 @@ var makeRunEventLog = (input) => {
|
|
|
33336
33277
|
};
|
|
33337
33278
|
|
|
33338
33279
|
// src/run/adapters/layout.ts
|
|
33339
|
-
import { join as
|
|
33280
|
+
import { join as join7 } from "path";
|
|
33340
33281
|
var safeRunId = (runId) => runId.replace(/[^a-zA-Z0-9_-]/g, "_") || "run";
|
|
33341
33282
|
var makeRunLayout = (workspace, runId) => {
|
|
33342
33283
|
const workspaceRoot = resolveWorkspacePath(workspace);
|
|
33343
|
-
const runRoot =
|
|
33344
|
-
const issueRoot =
|
|
33284
|
+
const runRoot = join7(workspaceRoot, ".ralphie", "runs", safeRunId(runId));
|
|
33285
|
+
const issueRoot = join7(runRoot, "issues");
|
|
33345
33286
|
return {
|
|
33346
33287
|
workspaceRoot,
|
|
33347
33288
|
runRoot,
|
|
33348
|
-
statePath:
|
|
33349
|
-
eventLogPath:
|
|
33350
|
-
issueArtifactsDirectory: (issueNumber) =>
|
|
33351
|
-
issueArtifactsPath: (issueNumber) =>
|
|
33352
|
-
diagnosticsDirectory: (issueNumber) =>
|
|
33353
|
-
diagnosticsPath: (issueNumber, name) =>
|
|
33289
|
+
statePath: join7(runRoot, "state.json"),
|
|
33290
|
+
eventLogPath: join7(runRoot, "events.jsonl"),
|
|
33291
|
+
issueArtifactsDirectory: (issueNumber) => join7(issueRoot, String(issueNumber)),
|
|
33292
|
+
issueArtifactsPath: (issueNumber) => join7(issueRoot, String(issueNumber), "artifacts.json"),
|
|
33293
|
+
diagnosticsDirectory: (issueNumber) => join7(issueRoot, String(issueNumber)),
|
|
33294
|
+
diagnosticsPath: (issueNumber, name) => join7(issueRoot, String(issueNumber), name)
|
|
33354
33295
|
};
|
|
33355
33296
|
};
|
|
33356
33297
|
|
package/docs/architecture.md
CHANGED
|
@@ -33,7 +33,7 @@ that binds concrete adapters into the runtime bundle.
|
|
|
33
33
|
| --- | --- | --- |
|
|
34
34
|
| `agent` | `src/agent/` | Prompts, structured-output and text-task helpers, and the role-to-session mapping (access mode, timeouts) over the harness port. Kept apart from `harness` on purpose: `agent` holds Ralphie's own prompts and role policy, while `harness` stays provider-neutral session plumbing that knows nothing about issues. |
|
|
35
35
|
| `config` | `src/config/` | YAML configuration: the zod schema (`settings.ts`), layering and `--set` overrides (`load.ts`, `overrides.ts`), and the file-reader port with its Bun YAML adapter. |
|
|
36
|
-
| `harness` | `src/harness/` | Provider-neutral harness port (session request, events, typed failures, structured results), the service that runs sessions and repairs invalid results, and one CLI adapter per harness (Claude Code, Codex, pi and OpenCode). Every workflow session runs through it
|
|
36
|
+
| `harness` | `src/harness/` | Provider-neutral harness port (session request, events, typed failures, structured results), the service that runs sessions and repairs invalid results, and one CLI adapter per harness (Claude Code, Codex, pi and OpenCode). Every workflow session runs through it with the read-only fingerprint guard and skill injection; the session environment inherits Ralphie's GitHub and git authority (see [Safety](safety.md#session-authority)). `app/roles.ts` resolves the configured role assignments. The vendored skills live in `vendor/mattpocock-skills/` (see [Development](development.md#vendored-skills)). |
|
|
37
37
|
| `github` | `src/github/` | Issue value objects, repository slug parsing, and the Octokit/`gh` adapters. |
|
|
38
38
|
| `git` | `src/git/` | Checkout preparation, checkpoints, issue operations, invariants, and remote-safety adapters. |
|
|
39
39
|
| `issues` | `src/issues/` | Domain (`domain/`), executors and artifact/recovery logic (`app/`), filesystem adapters (`adapters/`). |
|
|
@@ -105,11 +105,13 @@ Spike against OpenCode v2.0.22 (the published docs mostly describe v1). The reco
|
|
|
105
105
|
|
|
106
106
|
## Dependency and side-effect rules
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
Sessions handle reasoning and code edits; Ralphie's deterministic services own
|
|
109
|
+
the intended commit, push, tracker mutation, and delivery sequence. Prompts
|
|
110
|
+
instruct sessions not to commit, push, or mutate GitHub (see
|
|
111
|
+
[Session authority](safety.md#session-authority)). The adapters under `src/git/adapters/` and
|
|
112
|
+
`src/github/adapters/` perform Ralphie's side effects and verify their
|
|
113
|
+
invariants. The explicit runtime object makes this intended workflow
|
|
114
|
+
testable without a framework-specific execution model.
|
|
113
115
|
|
|
114
116
|
The `GitRepositoryFactsService` port in `src/git/ports.ts` reads HEAD, branch, status, recent log, and tracked files so read-only prompts, whose sessions have no shell, receive them as a `<repository-facts>` block.
|
|
115
117
|
|
package/docs/configuration.md
CHANGED
|
@@ -223,7 +223,8 @@ the first prepares the checkout, and the last to finish restores it. If the
|
|
|
223
223
|
repository has no `docs/agents/issue-tracker.md` or
|
|
224
224
|
`docs/agents/triage-labels.md`, Ralphie generates them for the session: the
|
|
225
225
|
label table comes from `labels`, and the tracker doc says issue content is in
|
|
226
|
-
the prompt
|
|
226
|
+
the prompt, permits `gh` reads, and instructs sessions not to mutate the
|
|
227
|
+
tracker. Committed versions always win.
|
|
227
228
|
|
|
228
229
|
### `limits`
|
|
229
230
|
|
package/docs/development.md
CHANGED
|
@@ -81,8 +81,12 @@ It is never run by `bun run test`, `bun run check`, or CI: it needs live
|
|
|
81
81
|
harness logins, model spend, and GitHub credentials. Run it by hand before a
|
|
82
82
|
release or after changing an adapter, prompt, or hand-off path:
|
|
83
83
|
|
|
84
|
+
Live runs are banned by default because they burn model quota: the script
|
|
85
|
+
exits `2` unless `RALPHIE_ALLOW_LIVE_SMOKE=1` is set. Only a human sets it;
|
|
86
|
+
agents must never do so.
|
|
87
|
+
|
|
84
88
|
```bash
|
|
85
|
-
RALPHIE_SMOKE_SCRATCH_REPO=you/ralphie-scratch \
|
|
89
|
+
RALPHIE_ALLOW_LIVE_SMOKE=1 RALPHIE_SMOKE_SCRATCH_REPO=you/ralphie-scratch \
|
|
86
90
|
bun run smoke:live -- --scratch-repo you/ralphie-scratch --harness claude,codex
|
|
87
91
|
```
|
|
88
92
|
|
|
@@ -203,10 +207,9 @@ Reviewing a sync pull request:
|
|
|
203
207
|
|
|
204
208
|
1. Read the upstream diff in the pull request (or its compare link) for each
|
|
205
209
|
driven skill, not only the vendored paths.
|
|
206
|
-
2. Check whether the new skill text
|
|
207
|
-
tool results
|
|
208
|
-
|
|
209
|
-
files.
|
|
210
|
+
2. Check whether the new skill text fits Ralphie's intended flow: schema-validated
|
|
211
|
+
tool results and direct delivery by deterministic services. Where skill text conflicts, change the Ralphie skill overlay in the same
|
|
212
|
+
pull request, never the vendored files.
|
|
210
213
|
3. Pull requests opened with the workflow's token do not trigger CI. Close and
|
|
211
214
|
reopen the pull request, or run `bun run check` on its branch, before merging.
|
|
212
215
|
|
|
@@ -220,7 +223,8 @@ Before submitting a change:
|
|
|
220
223
|
|
|
221
224
|
1. Add or update tests for the behavior.
|
|
222
225
|
2. Run `bun run check`.
|
|
223
|
-
3. Keep Git and GitHub
|
|
226
|
+
3. Keep Ralphie's intended Git and GitHub mutation workflow inside deterministic
|
|
227
|
+
domain services. See [Session authority](safety.md#session-authority).
|
|
224
228
|
4. Update the authoritative page under [`docs/`](README.md) when documentation
|
|
225
229
|
changes. Update [`CHANGELOG.md`](../CHANGELOG.md) when the command surface, the
|
|
226
230
|
output shape, the run-state version, or the recovery contract changes.
|
package/docs/getting-started.md
CHANGED
|
@@ -27,9 +27,8 @@ checkout. It brings its own login, so Ralphie asks for no model credentials
|
|
|
27
27
|
and stores none. Pick the harness, model and effort per role with the
|
|
28
28
|
`harnesses` and `roles` keys in the
|
|
29
29
|
[configuration file](configuration.md#harnesses-and-roles); without them every
|
|
30
|
-
role uses Claude Code with its own defaults. Sessions
|
|
31
|
-
|
|
32
|
-
[safety model](safety.md#agent-and-mutation-boundaries)).
|
|
30
|
+
role uses Claude Code with its own defaults. Sessions inherit Ralphie's GitHub
|
|
31
|
+
and git authority; see the [safety model](safety.md#session-authority).
|
|
33
32
|
|
|
34
33
|
For interactive GitHub authentication, run `gh auth login` and verify the
|
|
35
34
|
selected account with `gh auth status`. For unattended runs, supply a token
|
|
@@ -131,4 +130,4 @@ issue. See [Workflows](workflows.md) for what the selected route means and
|
|
|
131
130
|
[Operations and recovery](operations-and-recovery.md)
|
|
132
131
|
for the artifacts it leaves behind.
|
|
133
132
|
|
|
134
|
-
For all available options, continue to the [CLI reference](cli-reference.md).
|
|
133
|
+
For all available options, continue to the [CLI reference](cli-reference.md).
|
package/docs/safety.md
CHANGED
|
@@ -35,10 +35,12 @@ force-pushed over. Cancellation is checked at every mutation boundary, the push
|
|
|
35
35
|
is attempted at most once, and failures and cancellations leave a clean,
|
|
36
36
|
recoverable checkout.
|
|
37
37
|
|
|
38
|
-
Implementation
|
|
39
|
-
not filter their commands:
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
Implementation sessions may use whatever shell their harness grants. Ralphie
|
|
39
|
+
does not filter their commands: sessions inherit the GitHub and git authority
|
|
40
|
+
available to Ralphie, while prompts state the allowed behavior. The remaining
|
|
41
|
+
automated checks are the read-only fingerprint guard, checkpoint restore, and
|
|
42
|
+
delivery's remote verification and non-force push (see
|
|
43
|
+
[Session authority](#session-authority)).
|
|
42
44
|
|
|
43
45
|
## Workspace risk
|
|
44
46
|
|
|
@@ -132,43 +134,37 @@ Each session also has a wall-clock limit
|
|
|
132
134
|
([`limits.sessionTimeoutMinutes`](configuration.md#limits)); exceeding it kills
|
|
133
135
|
the session's whole process group, so tools the harness started die with it.
|
|
134
136
|
|
|
135
|
-
## Session
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
`GIT_TERMINAL_PROMPT=0`, and `GIT_SSH_COMMAND=false`, so a git remote cannot
|
|
150
|
-
authenticate through an ssh agent, a helper or a prompt. Ralphie's own
|
|
151
|
-
delivery push runs outside sessions and is unaffected.
|
|
152
|
-
- **Residual limitation.** The environment cannot hide credentials that a
|
|
153
|
-
process running as the same OS user can read directly: a private key under
|
|
154
|
-
`~/.ssh` used through an explicit `ssh -i`, a `gh` token read straight out of
|
|
155
|
-
the operating system keyring (for example with `security find-generic-password`
|
|
156
|
-
on macOS), or a credential file read by path. `gh` itself is logged out in a
|
|
157
|
-
session even when its token lives in the keyring, because it finds keyring
|
|
158
|
-
entries through the hosts file in its now-empty config directory. Ralphie does not claim to block these, and a determined
|
|
159
|
-
yolo session could still use them. What it guarantees is that it never hands
|
|
160
|
-
a token to a session, that the checkout's push URL is disabled, and that its
|
|
161
|
-
own delivery push is verified against the remote. To close the gap, run
|
|
162
|
-
Ralphie as a dedicated OS user whose home, keyring and ssh keys hold no
|
|
163
|
-
credentials for the repository.
|
|
164
|
-
- **No push from the workspace.** After preparing the checkout Ralphie sets
|
|
165
|
-
origin's push URL to a disabled value, so `git push` inside the workspace
|
|
166
|
-
fails. Ralphie's own delivery push names the fetch URL explicitly, never
|
|
167
|
-
uses force, and is verified against the remote afterwards.
|
|
168
|
-
- **Read-only means unchanged.** Before and after every read-only session
|
|
137
|
+
## Session authority
|
|
138
|
+
|
|
139
|
+
Every session inherits the GitHub and git authority available to Ralphie,
|
|
140
|
+
including its token or `gh auth login` profile and its git credentials. Sessions
|
|
141
|
+
may use `gh` to read issues, pull requests, comments and CI results. Their
|
|
142
|
+
prompts instruct them not to create, edit, comment on, label, close or reopen
|
|
143
|
+
GitHub issues, and not to commit or push. These are behavioral instructions,
|
|
144
|
+
not technical restrictions: a session that ignores them can mutate GitHub or
|
|
145
|
+
commit and push when the inherited credentials allow it. The `origin` push URL
|
|
146
|
+
is not disabled inside the workspace.
|
|
147
|
+
|
|
148
|
+
Ralphie retains three checks around session work:
|
|
149
|
+
|
|
150
|
+
- **Read-only fingerprint guard.** Before and after every read-only session,
|
|
169
151
|
Ralphie fingerprints HEAD, the index, tracked changes and untracked file
|
|
170
|
-
contents. Any difference fails the session (kind `access`)
|
|
171
|
-
|
|
152
|
+
contents. Any difference fails the session (kind `access`) and the issue.
|
|
153
|
+
- **Checkpoint restore.** On a failed issue outcome, Ralphie restores and
|
|
154
|
+
verifies the local checkout against its saved clean issue checkpoint. This
|
|
155
|
+
does not undo a GitHub mutation or a push a session already made.
|
|
156
|
+
- **Verified delivery.** Ralphie's delivery push is non-force, rechecks the
|
|
157
|
+
expected remote branch immediately before pushing, and verifies the remote
|
|
158
|
+
afterwards. Unexpected remote movement, including an early session push,
|
|
159
|
+
stops delivery instead of being adopted or overwritten. This detects the
|
|
160
|
+
movement; it does not reverse the session's push.
|
|
161
|
+
|
|
162
|
+
For a hard boundary on the credentials and repositories available to sessions,
|
|
163
|
+
run Ralphie under a dedicated OS user with only the intended credentials, or
|
|
164
|
+
use a fine-grained GitHub token scoped to the target repository with only the
|
|
165
|
+
permissions Ralphie needs. Sessions inherit those permissions. A repository
|
|
166
|
+
scope limits which repository the token can reach; it does not make access
|
|
167
|
+
read-only within that repository.
|
|
172
168
|
|
|
173
169
|
## Bounded command execution
|
|
174
170
|
|