fix(redact): order OPF prompt defaults · Entire
fix(redact): order OPF prompt defaults
1c559e1→main·
peyton-alt·4w ago·7 files·+59 added/-12 removed
Sessions
bbf03b229665View transcript
[?
OpenAI Privacy Filter Prompt DefaultsCodex·GPT-5.5·5 steps](/content/gh/entireio/cli/session/019edbd8-73f8-7981-8071-4156cbfaa27e#timeline-bbf03b229665/index.html)
Changes
7
cmd/entire/cli
settings
Msettings.go+5/-5
Msettings_test.go+2/-2
strategy
Mglobal_test.go+3
Mmanual_commit_opf_prompt.go+2/-2
Mmanual_commit_opf_prompt_test.go+42/-1
Mmanual_commit_push.go+4/-1
docs
Msecurity-and-privacy.md+1/-1
242 unmodified lines
243
244
245
246
247
246
247
248
249
250
1 unmodified line
252
253
254
255
255
256
257
258
259
733 unmodified lines
993
994
995
996
996
997
998
999
1000
1000
1001
1002
1003
242 unmodified lines
// PromptDefault controls whether the pre-push hook asks the user
// before running OPF. "" (default) and "ask" both surface the
// interactive prompt; "always" runs without asking; "never" skips
// OPF and pushes 7-layer content. ENTIRE_OPF=yes|no on the push
// interactive prompt; "never" skips OPF and pushes 7-layer content;
// "always" runs without asking. ENTIRE_OPF=yes|no on the push
// invocation overrides this setting per-push.
PromptDefault string `json:"prompt_default,omitempty"`
}
1 unmodified line
// Valid PromptDefault values. Empty == OPFPromptAsk.
const (
OPFPromptAsk = "ask"
OPFPromptAlways = "always"
OPFPromptNever = "never"
OPFPromptAlways = "always"
)
// GetCommitLinking returns the effective commit linking mode.
733 unmodified lines
return fmt.Errorf("openai_privacy_filter.timeout_seconds must be greater than or equal to 0 (got %d)", opf.TimeoutSeconds)
}
switch opf.PromptDefault {
case "", OPFPromptAsk, OPFPromptAlways, OPFPromptNever:
case "", OPFPromptAsk, OPFPromptNever, OPFPromptAlways:
// ok
default:
return fmt.Errorf("openai_privacy_filter.prompt_default must be one of %q, %q, %q (got %q)",
OPFPromptAsk, OPFPromptAlways, OPFPromptNever, opf.PromptDefault)
OPFPromptAsk, OPFPromptNever, OPFPromptAlways, opf.PromptDefault)
}
return nil
}
Mcmd/entire/cli/settings/settings.go+5/-5
954 unmodified lines
955
956
957
958
958
959
960
961
3 unmodified lines
965
966
967
968
968
969
970
971
972
954 unmodified lines
// TestLoadFromBytes_OPFSettings_PromptDefault covers parsing + validation
// of the prompt_default field added for the pre-push prompt UX. Empty is
// allowed (treated as "ask"); ask/always/never are the only valid values.
// allowed (treated as "ask"); ask/never/always are the only valid values.
func TestLoadFromBytes_OPFSettings_PromptDefault(t *testing.T) {
t.Parallel()
cases := []struct {
wantVal string
}{
{name: "ask", value: `"ask"`, wantVal: "ask"},
{name: "always", value: `"always"`, wantVal: "always"},
{name: "never", value: `"never"`, wantVal: "never"},
{name: "always", value: `"always"`, wantVal: "always"},
{name: "empty_string_allowed_as_ask", value: `""`, wantVal: ""},
{name: "bogus_value_rejected", value: `"sometimes"`, wantErr: true},
}
Mcmd/entire/cli/settings/settings_test.go+2/-2
1 unmodified line
2
3
4
5
6
7
8
4 unmodified lines
13
14
15
16
17
18
19
20
1 unmodified line
import (
"fmt"
"io"
"os"
"testing"
4 unmodified lines
)
func TestMain(m *testing.M) {
opfPrePushProgressWriter = io.Discard
// Register a default ConfigSource so tests that call ConfigScoped
// (directly or indirectly via Commit/CreateTag) don't fail with
// "no config loader registered".
Mcmd/entire/cli/strategy/global_test.go+3
40 unmodified lines
41
42
43
44
45
44
45
46
47
48
49
50
40 unmodified lines
return OPFSkip, nil
}
switch strings.ToLower(strings.TrimSpace(promptDefault)) {
case settings.OPFPromptAlways:
return OPFRun, nil
case settings.OPFPromptNever:
return OPFSkip, nil
case settings.OPFPromptAlways:
return OPFRun, nil
}
if !hasTTY {
// Non-interactive context: run OPF (matches the user's "if
Mcmd/entire/cli/strategy/manual_commit_opf_prompt.go+2/-2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
30 unmodified lines
49
50
51
49
52
53
54
55
56
94 unmodified lines
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package strategy
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"testing"
"github.com/entireio/cli/cmd/entire/cli/paths"
"github.com/entireio/cli/cmd/entire/cli/settings"
"github.com/entireio/cli/cmd/entire/cli/testutil"
"github.com/stretchr/testify/require"
)
30 unmodified lines
{name: "env_yes_case_insensitive", env: "YES", promptDefault: "", hasTTY: false, prompter: promptNever, want: OPFRun},
{name: "env_no_with_whitespace", env: " no ", promptDefault: "", hasTTY: false, prompter: promptNever, want: OPFSkip},
// Setting wins over prompt
{name: "setting_always_skips_prompt", env: "", promptDefault: settings.OPFPromptAlways, hasTTY: true, prompter: promptNever, want: OPFRun},
{name: "setting_never_skips_prompt", env: "", promptDefault: settings.OPFPromptNever, hasTTY: true, prompter: promptNever, want: OPFSkip},
{name: "setting_always_skips_prompt", env: "", promptDefault: settings.OPFPromptAlways, hasTTY: true, prompter: promptNever, want: OPFRun},
// Non-TTY fallback: run (matches the "if enabled, just run" semantics)
{name: "no_tty_auto_runs", env: "", promptDefault: "", hasTTY: false, prompter: promptNever, want: OPFRun},
{name: "no_tty_ignores_ask_setting", env: "", promptDefault: settings.OPFPromptAsk, hasTTY: false, prompter: promptNever, want: OPFRun},
94 unmodified lines
require.NoError(t, json.Unmarshal(got, &parsed))
require.Equal(t, settings.OPFPromptAlways, parsed.Redaction.OPF.PromptDefault)
}
// TestPrePush_OPFProgressUsesConfiguredWriter pins the test-noise escape hatch:
// PrePush still emits the non-interactive OPF progress notice in production,
// but tests can redirect it away from process stderr.
func TestPrePush_OPFProgressUsesConfiguredWriter(t *testing.T) {
tmpDir := t.TempDir()
testutil.InitRepo(t, tmpDir)
testutil.WriteFile(t, tmpDir, "f.txt", "init")
testutil.GitAdd(t, tmpDir, "f.txt")
testutil.GitCommit(t, tmpDir, "init")
require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, paths.EntireDir), 0o755))
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, paths.EntireDir, "settings.json"), []byte(`{
"enabled": true,
"redaction": {
"openai_privacy_filter": {
"enabled": true,
"categories": {"private_person": true}
}
}
}`), 0o644))
t.Chdir(tmpDir)
configureFakeOPF(t, &fakeOPFForRewrite{})
var out bytes.Buffer
withOPFPrePushProgressWriterForTest(t, &out)
require.NoError(t, (&ManualCommitStrategy{}).PrePush(t.Context(), "origin"))
require.Contains(t, out.String(), "OpenAI Privacy Filter: scanning checkpoints before push")
}
func withOPFPrePushProgressWriterForTest(t testing.TB, w io.Writer) {
t.Helper()
previous := opfPrePushProgressWriter
opfPrePushProgressWriter = w
t.Cleanup(func() {
opfPrePushProgressWriter = previous
})
}
Mcmd/entire/cli/strategy/manual_commit_opf_prompt_test.go+42/-1
2 unmodified lines
3
4
5
6
7
8
9
9 unmodified lines
19
20
21
22
23
24
25
26
29 unmodified lines
56
57
58
56
59
60
61
62
2 unmodified lines
import (
"context"
"errors"
"io"
"log/slog"
"os"
9 unmodified lines
// command propagates the non-zero exit code so git push aborts.
var errOPFAbortedByUser = errors.New("OPF prompt aborted by user; push cancelled")
var opfPrePushProgressWriter io.Writer = os.Stderr
// PrePush is called by the git pre-push hook before pushing to a remote.
// It pushes each ref in refs.Push alongside the user's push.
//
29 unmodified lines
if cfg != nil && cfg.Redaction != nil {
opfCfg = cfg.Redaction.OpenAIPrivacyFilter
}
decision, decisionErr := resolveOPFDecisionForPrePush(ctx, opfCfg, os.Stderr)
decision, decisionErr := resolveOPFDecisionForPrePush(ctx, opfCfg, opfPrePushProgressWriter)
if decisionErr != nil {
logging.Warn(ctx, "OPF pre-push decision failed; aborting push",
slog.String("error", decisionErr.Error()),
Mcmd/entire/cli/strategy/manual_commit_push.go+4/-1
127 unmodified lines
128
129
130
131
131
132
133
134
127 unmodified lines
- `command` — path or PATH-resolvable name of the `opf` binary. Defaults to `opf`.
- `timeout_seconds` — per-invocation timeout. Defaults to `30`.
- `prompt_default` — `"ask"` (default), `"always"`, or `"never"`. Controls whether the pre-push hook surfaces an interactive prompt before running OPF. `ENTIRE_OPF=yes` or `ENTIRE_OPF=no` on a single `git push` invocation overrides this for that push only.
- `prompt_default` — `"ask"` (default), `"never"`, or `"always"`. Controls whether the pre-push hook surfaces an interactive prompt before running OPF. `ENTIRE_OPF=yes` or `ENTIRE_OPF=no` on a single `git push` invocation overrides this for that push only.
The interactive prompt offers three options and reacts to **Ctrl-C** for cancellation:
Mdocs/security-and-privacy.md+1/-1