@agentsfleet/orly 0.10.2 → 0.10.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/package.json +1 -1
- package/src/config.test.ts +18 -0
- package/src/config.ts +19 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentsfleet/orly",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary \u2014 for any coding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/config.test.ts
CHANGED
|
@@ -180,6 +180,24 @@ describe("managedDrift — content", () => {
|
|
|
180
180
|
expect(drift[0]).toContain("move the change into the pack source");
|
|
181
181
|
});
|
|
182
182
|
|
|
183
|
+
test("a digest is algorithm-prefixed, and the unprefixed spelling still matches", async () => {
|
|
184
|
+
// Bare hex is indistinguishable from a credential to an entropy scanner:
|
|
185
|
+
// gitleaks refused the first commit that carried these, in every consuming
|
|
186
|
+
// repository at once. The prefix is what makes it pass without a scanner
|
|
187
|
+
// suppression — and 0.10.2's unprefixed records must not read as drift.
|
|
188
|
+
const bytes = new TextEncoder().encode("managed\n");
|
|
189
|
+
const digest = contentDigest(bytes);
|
|
190
|
+
expect(digest.startsWith("sha256:")).toBe(true);
|
|
191
|
+
|
|
192
|
+
const root = scratch();
|
|
193
|
+
writeFileSync(join(root, "managed.md"), "managed\n");
|
|
194
|
+
const legacy = { ...(await seedConfig(root)), managed: ["managed.md"], digests: { "managed.md": digest.slice("sha256:".length) } };
|
|
195
|
+
expect(managedDrift(root, legacy)).toEqual([]);
|
|
196
|
+
|
|
197
|
+
const prefixed = { ...legacy, digests: { "managed.md": digest } };
|
|
198
|
+
expect(managedDrift(root, prefixed)).toEqual([]);
|
|
199
|
+
});
|
|
200
|
+
|
|
183
201
|
test("a file with no recorded digest makes no claim", async () => {
|
|
184
202
|
// Two cases share this path: a config written before digests existed, and a
|
|
185
203
|
// hook orly manages but did not author. Neither may be reported as drift.
|
package/src/config.ts
CHANGED
|
@@ -130,8 +130,25 @@ function readDigests(value: unknown): Record<string, string> {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/// The digest orly records for content it wrote, and recomputes to check it.
|
|
133
|
+
///
|
|
134
|
+
/// Algorithm-prefixed, and not for elegance: a bare 64-character hex string is
|
|
135
|
+
/// indistinguishable from a credential to an entropy-based secret scanner, and
|
|
136
|
+
/// gitleaks refused the very first commit that carried these — in EVERY
|
|
137
|
+
/// consuming repository at once, since `orly update` writes one per managed
|
|
138
|
+
/// file. The prefix is what makes the value self-describing enough to pass,
|
|
139
|
+
/// and the alternative was a scanner suppression, which is how a real
|
|
140
|
+
/// credential eventually rides through.
|
|
133
141
|
export function contentDigest(bytes: Uint8Array): string {
|
|
134
|
-
return createHash(DIGEST_ALGORITHM).update(bytes).digest(DIGEST_ENCODING)
|
|
142
|
+
return `${DIGEST_ALGORITHM}:${createHash(DIGEST_ALGORITHM).update(bytes).digest(DIGEST_ENCODING)}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Tolerates the unprefixed spelling 0.10.2 wrote for its one release, so a
|
|
146
|
+
// checkout installed on that version is not reported as wholly drifted before
|
|
147
|
+
// its next update — a format change is not an edit, and saying so would be the
|
|
148
|
+
// false alarm that teaches people to ignore the real ones.
|
|
149
|
+
function sameDigest(recorded: string, actual: string): boolean {
|
|
150
|
+
const bare = (digest: string) => digest.startsWith(`${DIGEST_ALGORITHM}:`) ? digest.slice(DIGEST_ALGORITHM.length + 1) : digest;
|
|
151
|
+
return bare(recorded) === bare(actual);
|
|
135
152
|
}
|
|
136
153
|
|
|
137
154
|
// What orly installed here, and whether the engine has moved since.
|
|
@@ -196,7 +213,7 @@ function editedManaged(targetRoot: string, config: RepoConfig, present: string[]
|
|
|
196
213
|
const recorded = config.digests[relativePath];
|
|
197
214
|
if (!recorded) return [];
|
|
198
215
|
const actual = contentDigest(readFileSync(join(targetRoot, relativePath)));
|
|
199
|
-
if (actual
|
|
216
|
+
if (sameDigest(recorded, actual)) return [];
|
|
200
217
|
return [`managed file was edited after orly wrote it: ${relativePath} — move the change into the pack source, or \`orly update\` to discard it`];
|
|
201
218
|
});
|
|
202
219
|
}
|