From 0dc8ff9b52acd23e718b3de7770e900729ff4a44 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 29 Aug 2026 20:45:27 +0000 Subject: [PATCH] Exclude external paths from analysis fingerprints --- docs/internal/stage-analyze.md | 11 +++++------ internal/stage/analyze_fingerprint.go | 22 ---------------------- internal/stage/analyze_fingerprint_test.go | 10 ++-------- 3 files changed, 7 insertions(+), 36 deletions(-) diff --git a/docs/internal/stage-analyze.md b/docs/internal/stage-analyze.md index 3b4a61e..fb6c1cc 100644 --- a/docs/internal/stage-analyze.md +++ b/docs/internal/stage-analyze.md @@ -67,11 +67,10 @@ Supported source families: JSON payload and no map serialization. Configured artifacts are fingerprinted in deterministic dependency order. - fingerprints the normalized artifact key, prompt and profile identifiers, - normalized Scriptorium executable and config logical identities, effective - render-debug behavior, session-relative output identity, sorted dependency - keys, ordered input declarations and semantic identities, validated current - dependency-output identities, and sorted effective Scriptorium variables - (including Narratio's sticky session variable). + effective render-debug behavior, session-relative output identity, sorted + dependency keys, ordered input declarations and semantic identities, + validated current dependency-output identities, and sorted effective + Scriptorium variables (including Narratio's sticky session variable). - provides read-only reconciliation that classifies each configured record as current, stale, missing, failed, legacy, or otherwise non-resumable, and separately identifies manifest records removed from current configuration. @@ -155,7 +154,7 @@ Supported source families: - input-identity resolution is read-only: it does not invoke adapters, materialize outputs, update status, or create run records. - fingerprints exclude timeouts, retries, timestamps, producer and Narratio run - IDs, absolute executable/config/workspace roots, diagnostic locations, and + IDs, executable and config paths, workspace roots, diagnostic locations, and executable or private transitive configuration contents. A change that is visible only inside Scriptorium—such as a file privately loaded by its config path—requires an explicit forced regeneration. diff --git a/internal/stage/analyze_fingerprint.go b/internal/stage/analyze_fingerprint.go index dde221f..7f60183 100644 --- a/internal/stage/analyze_fingerprint.go +++ b/internal/stage/analyze_fingerprint.go @@ -49,18 +49,12 @@ type analyzeDependencyOutputIdentity struct { // as SHA-256 input. It contains slices and fixed-field structs, never maps. type analyzeFingerprintPayload struct { Version int `json:"version"` - Scriptorium analyzeFingerprintScriptorium `json:"scriptorium"` Artifact analyzeFingerprintArtifact `json:"artifact"` Inputs []analyzeFingerprintInput `json:"inputs"` DependencyOutputs []analyzeFingerprintDependencyOutput `json:"dependency_outputs"` Variables []analyzeFingerprintVariable `json:"variables"` } -type analyzeFingerprintScriptorium struct { - ExecutableIdentity string `json:"executable_identity"` - ConfigIdentity string `json:"config_identity"` -} - type analyzeFingerprintArtifact struct { Key string `json:"key"` PromptID string `json:"prompt_id"` @@ -169,10 +163,6 @@ func computeAnalyzeArtifactFingerprint( } payload := analyzeFingerprintPayload{ Version: manifest.AnalyzeFingerprintContractVersion, - Scriptorium: analyzeFingerprintScriptorium{ - ExecutableIdentity: normalizedAnalyzeExternalPathIdentity(scriptoriumCfg.Binary), - ConfigIdentity: normalizedAnalyzeExternalPathIdentity(scriptoriumCfg.ConfigPath), - }, Artifact: analyzeFingerprintArtifact{ Key: key, PromptID: artifactCfg.PromptID, ProfileID: artifactCfg.ProfileID, EffectiveRenderDebug: resolveRenderDebugEnabled(scriptoriumCfg.RenderDebug, artifactCfg.RenderDebug), @@ -260,18 +250,6 @@ func normalizedAnalyzeDependencyKeys(values []string) []string { return result } -func normalizedAnalyzeExternalPathIdentity(value string) string { - trimmed := strings.TrimSpace(value) - if trimmed == "" { - return "" - } - cleaned := filepath.Clean(trimmed) - if filepath.IsAbs(cleaned) { - return filepath.Base(cleaned) - } - return filepath.ToSlash(cleaned) -} - func normalizedAnalyzeOutputIdentity(value string) (string, error) { trimmed := strings.TrimSpace(value) if trimmed == "" { diff --git a/internal/stage/analyze_fingerprint_test.go b/internal/stage/analyze_fingerprint_test.go index 25b320f..844cf61 100644 --- a/internal/stage/analyze_fingerprint_test.go +++ b/internal/stage/analyze_fingerprint_test.go @@ -18,8 +18,6 @@ func TestAnalyzeFingerprintCanonicalPayloadSensitivity(t *testing.T) { mutate func(*analyzeFingerprintPayload) }{ {name: "version", mutate: func(p *analyzeFingerprintPayload) { p.Version++ }}, - {name: "scriptorium executable", mutate: func(p *analyzeFingerprintPayload) { p.Scriptorium.ExecutableIdentity = "other" }}, - {name: "scriptorium config", mutate: func(p *analyzeFingerprintPayload) { p.Scriptorium.ConfigIdentity = "other.yml" }}, {name: "artifact key", mutate: func(p *analyzeFingerprintPayload) { p.Artifact.Key = "other" }}, {name: "prompt", mutate: func(p *analyzeFingerprintPayload) { p.Artifact.PromptID = "prompt.changed" }}, {name: "profile", mutate: func(p *analyzeFingerprintPayload) { p.Artifact.ProfileID = "profile.changed" }}, @@ -87,7 +85,8 @@ func TestComputeAnalyzeFingerprintsIsDeterministicAcrossIncidentalDifferences(t {name: "producer run", options: fingerprintFixtureOptions{producerRunID: "run-b", binary: "/opt/a/scriptorium", configPath: "/etc/a/scriptorium.yml", topTimeout: "1m", artifactTimeout: "2m", updatedAt: time.Unix(100, 0).UTC()}}, {name: "timestamp", options: fingerprintFixtureOptions{producerRunID: "run-a", binary: "/opt/a/scriptorium", configPath: "/etc/a/scriptorium.yml", topTimeout: "1m", artifactTimeout: "2m", updatedAt: time.Unix(999, 0).UTC()}}, {name: "timeouts", options: fingerprintFixtureOptions{producerRunID: "run-a", binary: "/opt/a/scriptorium", configPath: "/etc/a/scriptorium.yml", topTimeout: "8m", artifactTimeout: "9m", updatedAt: time.Unix(100, 0).UTC()}}, - {name: "absolute executable and config paths", options: fingerprintFixtureOptions{producerRunID: "run-a", binary: "/srv/b/scriptorium", configPath: "/srv/b/scriptorium.yml", topTimeout: "1m", artifactTimeout: "2m", updatedAt: time.Unix(100, 0).UTC()}}, + {name: "absolute executable and config paths", options: fingerprintFixtureOptions{producerRunID: "run-a", binary: "/srv/b/scriptorium-v2", configPath: "/srv/b/alternate.yml", topTimeout: "1m", artifactTimeout: "2m", updatedAt: time.Unix(100, 0).UTC()}}, + {name: "relative executable and config paths", options: fingerprintFixtureOptions{producerRunID: "run-a", binary: "bin/scriptorium-v2", configPath: "config/alternate.yml", topTimeout: "1m", artifactTimeout: "2m", updatedAt: time.Unix(100, 0).UTC()}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -109,8 +108,6 @@ func TestComputeAnalyzeFingerprintsTracksEffectiveConfiguration(t *testing.T) { {name: "render debug", options: fingerprintFixtureOptions{producerRunID: "run-a", dependencyBody: "notes\n", renderDebug: true}}, {name: "output", options: fingerprintFixtureOptions{producerRunID: "run-a", dependencyBody: "notes\n", outputPath: "artifacts/changed.md"}}, {name: "effective variable", options: fingerprintFixtureOptions{producerRunID: "run-a", dependencyBody: "notes\n", label: "changed"}}, - {name: "executable identity", options: fingerprintFixtureOptions{producerRunID: "run-a", dependencyBody: "notes\n", binary: "/opt/bin/other"}}, - {name: "config identity", options: fingerprintFixtureOptions{producerRunID: "run-a", dependencyBody: "notes\n", configPath: "/etc/scriptorium/other.yml"}}, {name: "dependency bytes", options: fingerprintFixtureOptions{producerRunID: "run-a", dependencyBody: "other\n"}}, {name: "optional input appears", options: fingerprintFixtureOptions{producerRunID: "run-a", dependencyBody: "notes\n", includePlayers: true}}, } @@ -170,9 +167,6 @@ func analyzeFingerprintPayloadFixture() analyzeFingerprintPayload { } return analyzeFingerprintPayload{ Version: manifest.AnalyzeFingerprintContractVersion, - Scriptorium: analyzeFingerprintScriptorium{ - ExecutableIdentity: "scriptorium", ConfigIdentity: "config.yml", - }, Artifact: analyzeFingerprintArtifact{ Key: "session_recap", PromptID: "dnd.session_recap", ProfileID: "local", EffectiveRenderDebug: true, OutputIdentity: "artifacts/session_recap.md",