feat(web/moderation): Ada-as-tagger + retire off_topic — POLICY_PROMPT_V=2#11
Merged
Conversation
…T_V=2
Two structural changes that ride together because both bump the
prompt version stamp on policy_decisions.
1. Ada gains a tagging job. The moderator emits up to 2 topical
tags for accepted submissions in the same call. Hybrid vocab:
pick from existing tags (is_new=false) or propose new tags with
pending_review=true for staff approval at /admin/flags. Migration
0022 adds tags.pending_review and submission_tags.source
('ai'|'user') with a CHECK constraint. Pending tags hide from /c
and from public submission rows; staff approve/reject in the new
pending-review section. tagSlugSchema in lib/tags/slug.ts is the
one canonical slug shape — moderator parsing, admin actions, and
the public API now share it.
2. POLICY_CATEGORIES drops off_topic. The gate now handles universal
trust-and-safety only (spam/abuse/illegal/doxxing). Topical fit is
the editorial layer's concern (rubric scoring + voting), not the
moderator's. Removes a perpetual drift point for autonomous
operation and fixes the misread where gpt-4o-mini was rejecting
AI-on-topic content as off-topic-for-AI-audience due to an
ambiguous parenthetical in the fallback prompt.
POLICY_PROMPT_V bumped 1→2 so policy_decisions.prompt_version
splits analytics cleanly across the change. The category column is
plain text not enum, so legacy rows with category='off_topic' stay
queryable; display surfaces (/office/policy, /appeal/[id]) keep
their off_topic labels for historical rendering.
Migration 0022 is NOT auto-applied — apply via psql per
.claude/rules/db-migrations.md. drizzle-kit push against prod would
drop the search_vec generated column.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
HIGH fixes: - register_from_browser: rollback private blob + cleanup temp dir on store.insert() failure (#4) - Orphan detection: roundtrip check prevents lossy unsanitize_path from causing false-positive deletions (#1, #10) - --from-token reads from stdin when value is "-" to avoid shell history exposure (#9) MEDIUM fixes: - swap switch(): rollback CC credentials on set_active_cli failure (#11) - register_account_from_profile: delete orphaned private blob on insert failure (#17) - remove_account: reorder to clear pointers + remove DB row before irreversible file deletions (#18) - desktop switch(): propagate DB update failures instead of ignoring (#22) - account.rs: propagate DB permission-setting failures (#20) - project.rs: post-move failures become warnings instead of errors when directory already moved (#16) - account remove --json: emit structured JSON response (#28) - doctor summary: count expired accounts as warnings (#26) LOW fixes: - doctor: surface store.list() failures via db_error field (#29) - keychain status: distinguish "no credential" from "access error" (#30)
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
#15 REGRESSED — metrics transition-only broke active_series carry-forward: * Runtime now writes on transition OR heartbeat (every 60 ticks at 500ms cadence = 30s). Bounded volume (~1/30s/session for idle-static sessions, more for transitioning ones) but preserves per-bucket density so the Trends active_series query still sees each live session in every bucket longer than the heartbeat interval. * SessionState.ticks_since_metrics counter drives the heartbeat. Reset to 0 on every write; increments every tick otherwise. * New runtime test: metrics_writes_transition_plus_heartbeat against a MetricsStore tempdir confirms two distinct writes produce two non-zero buckets. #11 PARTIAL — LiveStatusHeader ignored detail-channel deltas: * status_changed / overlay_changed / model_changed now update local override state (setLiveStatus / setLiveOverlay / setLiveModel / setLiveWaitingFor). Rendered values prefer the detail override when present and fall back to the aggregate summary. * seq-guarded: lastSeqRef keeps late out-of-order deliveries from overwriting a newer state. * StatusChipRow refactored to accept broken-out props so it can consume either source without a synthetic summary object. #17 PARTIAL — j/k still used a global window listener: * Removed the useEffect + window.addEventListener pair. * Replaced with a local handler attached directly to the role=listbox div. No global listener at all; / pressed anywhere outside the strip simply don't reach our handler. useEffect import dropped. #20 PARTIAL — lifecycle tests missing: * excluded_paths_are_skipped_by_tick — proves the runtime filters excluded projects before attach. * unsubscribe_releases_detail_slot_for_resubscribe — proves the single-subscriber contract, the AlreadySubscribed error, and that detail_end_session clears the slot. * metrics_writes_transition_plus_heartbeat — proves the post- audit write model doesn't double-count and does record both transitions. Full tests: 689 core + 3 E2E + 36 tauri-lib = 728 Rust; 161 React. tsc --noEmit clean. cargo check --workspace clean.
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
Independent Codex audit (5-dim mini, threadId 019de662) flagged 2 High + 8 Medium + 1 Low across the templates feature surface. Independent verification (threadId 019de792) confirmed 10 fixes landed; #11 is deferred with rationale. ### High #1 prerun probe ignored route auth — `probe_sync` in `automations/prerun.rs` now attaches `Authorization: Bearer <key>` (or `Basic <key>` when `auth_scheme` is set) from the route's `GatewayConfig.api_key` when non-empty. LiteLLM / Cloudflare AI Gateway endpoints behind auth no longer false- fail with 401. #2 sidecar swap in `templates_apply_pending` — `commands_templates.rs` now rejects early when `pending.automation_id != automation_id`. Without this, an attacker (or a buggy caller) could pair automation A's pending- changes.json with automation B's broader apply policy. ### Medium #3 Ollama probe detection too broad — was `cfg.base_url.contains ("/api/")` which false-positives on `/api/v1` (OpenRouter, LiteLLM). Now narrowed to `:11434` port OR explicit `/api/tags` path. #4 hardcoded `~/.claudepot` — both `templates_pending_changes` and `templates_read_report` now use `claudepot_data_dir()` from claudepot-core, honoring `CLAUDEPOT_DATA_DIR` overrides correctly. #5 `supported_platforms` was only enforced in `templates_list` — `templates_install` now ALSO rejects when `!bp.supports(HostPlatform::current())`. The contract is symmetric: a direct IPC bypass of the gallery filter no longer lets a macOS-only template instantiate on Linux/Windows. #6 Windows ACL trusted `USERNAME` — `tighten_consent_acl` now resolves the current user SID via `whoami /user /fo csv /nh`, grants `*<SID>:F` in icacls (well-known SID notation), grants SYSTEM via the literal `S-1-5-18` SID, and surfaces every failure path through `tracing::warn` instead of swallowing silently. #7 cron test wrote `CLAUDE_OAUTH_TOKEN` to disk via `automation.extra_env` (which `install_shim` renders into `run.sh`). Token now lives in a 0600 sibling file (`<tmp>/.oauth-token`); a 0700 wrapper script (`<tmp>/claude-with-token.sh`) reads the file at fire time and exec's the real claude. The shim sees only the wrapper path, never the token. #8 cron test scheduled `now + 1 minute`, which races the case where `install_shim + scheduler.register` span past the next minute boundary — launchd would defer to the same time tomorrow and the test would hang. Now schedules `now + 2 minutes`; deadline extended to 180s. #9 cron test passed on a `"result":` substring match, which is too permissive — error rows mention `result` too. Now parses `stdout.log` as a JSON event array, finds the terminal `result` event, asserts `is_error == false`. Verified locally: the cron run produced `is_error=false result_first_80= CRON_TEMPLATE_OK`. #10 `CronCleanupGuard` did not restore the previous `CLAUDEPOT_DATA_DIR` — risk of polluting subsequent tests in the same process. Now captures the prior value as `Option<OsString>` at construction and restores it on Drop. Also explicitly zero-fills + unlinks the token file so a tempdir-removal race doesn't leave the token on disk. ### Low (deferred) #11 validator rebuilds globset matcher per call. Apply is interactive (~5-50 items × 1-3 globs each, user reviews + clicks per run); not a hot loop. Caching would require an `ApplyConfig`-level matcher cache and is not worth the complexity. Documented in the audit-fix verification report. ### Verification Independent Codex re-audit on a fresh thread: | Status | Count | |---|---:| | FIXED | 10 | | NOT FIXED | 0 | | PARTIAL | 0 | | REGRESSED | 0 | | DEFERRED | 1 | Local validation: - `cargo test --workspace` — all green - `cargo test -p claudepot-core --test templates_real_llm cron_schedule_fires -- --ignored` — passes with new is_error=false assertion (110s wall, ~$0.30) Audit threadId: 019de662-808c-7b62-80b3-e85d373f92b5 Verify threadId: 019de792-3515-7533-aac0-0203a96388be
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
feat(web/moderation): Ada-as-tagger + retire off_topic — POLICY_PROMPT_V=2
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
Independent Codex audit (5-dim mini, threadId 019de662) flagged 2 High + 8 Medium + 1 Low across the templates feature surface. Independent verification (threadId 019de792) confirmed 10 fixes landed; #11 is deferred with rationale. ### High #1 prerun probe ignored route auth — `probe_sync` in `automations/prerun.rs` now attaches `Authorization: Bearer <key>` (or `Basic <key>` when `auth_scheme` is set) from the route's `GatewayConfig.api_key` when non-empty. LiteLLM / Cloudflare AI Gateway endpoints behind auth no longer false- fail with 401. #2 sidecar swap in `templates_apply_pending` — `commands_templates.rs` now rejects early when `pending.automation_id != automation_id`. Without this, an attacker (or a buggy caller) could pair automation A's pending- changes.json with automation B's broader apply policy. ### Medium #3 Ollama probe detection too broad — was `cfg.base_url.contains ("/api/")` which false-positives on `/api/v1` (OpenRouter, LiteLLM). Now narrowed to `:11434` port OR explicit `/api/tags` path. #4 hardcoded `~/.claudepot` — both `templates_pending_changes` and `templates_read_report` now use `claudepot_data_dir()` from claudepot-core, honoring `CLAUDEPOT_DATA_DIR` overrides correctly. #5 `supported_platforms` was only enforced in `templates_list` — `templates_install` now ALSO rejects when `!bp.supports(HostPlatform::current())`. The contract is symmetric: a direct IPC bypass of the gallery filter no longer lets a macOS-only template instantiate on Linux/Windows. #6 Windows ACL trusted `USERNAME` — `tighten_consent_acl` now resolves the current user SID via `whoami /user /fo csv /nh`, grants `*<SID>:F` in icacls (well-known SID notation), grants SYSTEM via the literal `S-1-5-18` SID, and surfaces every failure path through `tracing::warn` instead of swallowing silently. #7 cron test wrote `CLAUDE_OAUTH_TOKEN` to disk via `automation.extra_env` (which `install_shim` renders into `run.sh`). Token now lives in a 0600 sibling file (`<tmp>/.oauth-token`); a 0700 wrapper script (`<tmp>/claude-with-token.sh`) reads the file at fire time and exec's the real claude. The shim sees only the wrapper path, never the token. #8 cron test scheduled `now + 1 minute`, which races the case where `install_shim + scheduler.register` span past the next minute boundary — launchd would defer to the same time tomorrow and the test would hang. Now schedules `now + 2 minutes`; deadline extended to 180s. #9 cron test passed on a `"result":` substring match, which is too permissive — error rows mention `result` too. Now parses `stdout.log` as a JSON event array, finds the terminal `result` event, asserts `is_error == false`. Verified locally: the cron run produced `is_error=false result_first_80= CRON_TEMPLATE_OK`. #10 `CronCleanupGuard` did not restore the previous `CLAUDEPOT_DATA_DIR` — risk of polluting subsequent tests in the same process. Now captures the prior value as `Option<OsString>` at construction and restores it on Drop. Also explicitly zero-fills + unlinks the token file so a tempdir-removal race doesn't leave the token on disk. ### Low (deferred) #11 validator rebuilds globset matcher per call. Apply is interactive (~5-50 items × 1-3 globs each, user reviews + clicks per run); not a hot loop. Caching would require an `ApplyConfig`-level matcher cache and is not worth the complexity. Documented in the audit-fix verification report. ### Verification Independent Codex re-audit on a fresh thread: | Status | Count | |---|---:| | FIXED | 10 | | NOT FIXED | 0 | | PARTIAL | 0 | | REGRESSED | 0 | | DEFERRED | 1 | Local validation: - `cargo test --workspace` — all green - `cargo test -p claudepot-core --test templates_real_llm cron_schedule_fires -- --ignored` — passes with new is_error=false assertion (110s wall, ~$0.30) Audit threadId: 019de662-808c-7b62-80b3-e85d373f92b5 Verify threadId: 019de792-3515-7533-aac0-0203a96388be
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
feat(web/moderation): Ada-as-tagger + retire off_topic — POLICY_PROMPT_V=2
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
HIGH fixes: - register_from_browser: rollback private blob + cleanup temp dir on store.insert() failure (#4) - Orphan detection: roundtrip check prevents lossy unsanitize_path from causing false-positive deletions (#1, #10) - --from-token reads from stdin when value is "-" to avoid shell history exposure (#9) MEDIUM fixes: - swap switch(): rollback CC credentials on set_active_cli failure (#11) - register_account_from_profile: delete orphaned private blob on insert failure (#17) - remove_account: reorder to clear pointers + remove DB row before irreversible file deletions (#18) - desktop switch(): propagate DB update failures instead of ignoring (#22) - account.rs: propagate DB permission-setting failures (#20) - project.rs: post-move failures become warnings instead of errors when directory already moved (#16) - account remove --json: emit structured JSON response (#28) - doctor summary: count expired accounts as warnings (#26) LOW fixes: - doctor: surface store.list() failures via db_error field (#29) - keychain status: distinguish "no credential" from "access error" (#30)
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
#15 REGRESSED — metrics transition-only broke active_series carry-forward: * Runtime now writes on transition OR heartbeat (every 60 ticks at 500ms cadence = 30s). Bounded volume (~1/30s/session for idle-static sessions, more for transitioning ones) but preserves per-bucket density so the Trends active_series query still sees each live session in every bucket longer than the heartbeat interval. * SessionState.ticks_since_metrics counter drives the heartbeat. Reset to 0 on every write; increments every tick otherwise. * New runtime test: metrics_writes_transition_plus_heartbeat against a MetricsStore tempdir confirms two distinct writes produce two non-zero buckets. #11 PARTIAL — LiveStatusHeader ignored detail-channel deltas: * status_changed / overlay_changed / model_changed now update local override state (setLiveStatus / setLiveOverlay / setLiveModel / setLiveWaitingFor). Rendered values prefer the detail override when present and fall back to the aggregate summary. * seq-guarded: lastSeqRef keeps late out-of-order deliveries from overwriting a newer state. * StatusChipRow refactored to accept broken-out props so it can consume either source without a synthetic summary object. #17 PARTIAL — j/k still used a global window listener: * Removed the useEffect + window.addEventListener pair. * Replaced with a local handler attached directly to the role=listbox div. No global listener at all; / pressed anywhere outside the strip simply don't reach our handler. useEffect import dropped. #20 PARTIAL — lifecycle tests missing: * excluded_paths_are_skipped_by_tick — proves the runtime filters excluded projects before attach. * unsubscribe_releases_detail_slot_for_resubscribe — proves the single-subscriber contract, the AlreadySubscribed error, and that detail_end_session clears the slot. * metrics_writes_transition_plus_heartbeat — proves the post- audit write model doesn't double-count and does record both transitions. Full tests: 689 core + 3 E2E + 36 tauri-lib = 728 Rust; 161 React. tsc --noEmit clean. cargo check --workspace clean.
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
Independent Codex audit (5-dim mini, threadId 019de662) flagged 2 High + 8 Medium + 1 Low across the templates feature surface. Independent verification (threadId 019de792) confirmed 10 fixes landed; #11 is deferred with rationale. ### High #1 prerun probe ignored route auth — `probe_sync` in `automations/prerun.rs` now attaches `Authorization: Bearer <key>` (or `Basic <key>` when `auth_scheme` is set) from the route's `GatewayConfig.api_key` when non-empty. LiteLLM / Cloudflare AI Gateway endpoints behind auth no longer false- fail with 401. #2 sidecar swap in `templates_apply_pending` — `commands_templates.rs` now rejects early when `pending.automation_id != automation_id`. Without this, an attacker (or a buggy caller) could pair automation A's pending- changes.json with automation B's broader apply policy. ### Medium #3 Ollama probe detection too broad — was `cfg.base_url.contains ("/api/")` which false-positives on `/api/v1` (OpenRouter, LiteLLM). Now narrowed to `:11434` port OR explicit `/api/tags` path. #4 hardcoded `~/.claudepot` — both `templates_pending_changes` and `templates_read_report` now use `claudepot_data_dir()` from claudepot-core, honoring `CLAUDEPOT_DATA_DIR` overrides correctly. #5 `supported_platforms` was only enforced in `templates_list` — `templates_install` now ALSO rejects when `!bp.supports(HostPlatform::current())`. The contract is symmetric: a direct IPC bypass of the gallery filter no longer lets a macOS-only template instantiate on Linux/Windows. #6 Windows ACL trusted `USERNAME` — `tighten_consent_acl` now resolves the current user SID via `whoami /user /fo csv /nh`, grants `*<SID>:F` in icacls (well-known SID notation), grants SYSTEM via the literal `S-1-5-18` SID, and surfaces every failure path through `tracing::warn` instead of swallowing silently. #7 cron test wrote `CLAUDE_OAUTH_TOKEN` to disk via `automation.extra_env` (which `install_shim` renders into `run.sh`). Token now lives in a 0600 sibling file (`<tmp>/.oauth-token`); a 0700 wrapper script (`<tmp>/claude-with-token.sh`) reads the file at fire time and exec's the real claude. The shim sees only the wrapper path, never the token. #8 cron test scheduled `now + 1 minute`, which races the case where `install_shim + scheduler.register` span past the next minute boundary — launchd would defer to the same time tomorrow and the test would hang. Now schedules `now + 2 minutes`; deadline extended to 180s. #9 cron test passed on a `"result":` substring match, which is too permissive — error rows mention `result` too. Now parses `stdout.log` as a JSON event array, finds the terminal `result` event, asserts `is_error == false`. Verified locally: the cron run produced `is_error=false result_first_80= CRON_TEMPLATE_OK`. #10 `CronCleanupGuard` did not restore the previous `CLAUDEPOT_DATA_DIR` — risk of polluting subsequent tests in the same process. Now captures the prior value as `Option<OsString>` at construction and restores it on Drop. Also explicitly zero-fills + unlinks the token file so a tempdir-removal race doesn't leave the token on disk. ### Low (deferred) #11 validator rebuilds globset matcher per call. Apply is interactive (~5-50 items × 1-3 globs each, user reviews + clicks per run); not a hot loop. Caching would require an `ApplyConfig`-level matcher cache and is not worth the complexity. Documented in the audit-fix verification report. ### Verification Independent Codex re-audit on a fresh thread: | Status | Count | |---|---:| | FIXED | 10 | | NOT FIXED | 0 | | PARTIAL | 0 | | REGRESSED | 0 | | DEFERRED | 1 | Local validation: - `cargo test --workspace` — all green - `cargo test -p claudepot-core --test templates_real_llm cron_schedule_fires -- --ignored` — passes with new is_error=false assertion (110s wall, ~$0.30) Audit threadId: 019de662-808c-7b62-80b3-e85d373f92b5 Verify threadId: 019de792-3515-7533-aac0-0203a96388be
xiaolai
added a commit
that referenced
this pull request
May 8, 2026
feat(web/moderation): Ada-as-tagger + retire off_topic — POLICY_PROMPT_V=2
xiaolai
added a commit
that referenced
this pull request
May 18, 2026
Eight findings collapsed into one refactor cycle plus two small cleanups. The grill report (in conversation; not on disk) flagged 0 critical / 0 high / 3 medium / 8 low — this addresses every actionable item. Core reshape (#1 + #6 + #7): - detect_pr returns DetectOutcome { branch, pr } so the orchestrator keys the cache directly. Halves the per-tick git invocation count (no more standalone current_branch call). - PrCache keyed by repo_root only; insert() overwrites unconditionally so a branch flip is absorbed by the next tick without explicit detection. Drops get_any_for (was O(n)), drops the dead Entry.branch field, drops refresh_one's early-return guard. Orchestrator parallelism + gh latching (#2 + #9): - tick_all fans out via tokio Semaphore + JoinSet with MAX_PARALLEL=4 cap on simultaneous gh invocations. 30-project tick now completes in ~8 batches instead of 30 sequential calls. - First MissingCli("gh") flips an AtomicBool that short-circuits every subsequent tick. gh-less users pay one detect attempt per process lifetime, then nothing. Test coverage (#8): - New PrDetector trait + RealDetector wrapper so the orchestrator is testable against a FakeDetector. Four new tokio tests cover cache round-trip, negative caching, gh-absent latch, and the bounded-parallel fan-out (12 roots vs MAX_PARALLEL=4). - DTO tests: PrInfoDto serializes PrState lowercase; ProjectInfoDto omits the pr field when None. - PrCache test for explicit overwrite semantics. UI hygiene (#3, #4, #5, #11): - Extract liveDotTitle to src/components/primitives so ProjectDetail and ProjectsTable share one implementation. - LiveStatusDot.title is now required in the prop type — the "mandatory by comment" contract was decay-prone. - BrandGithubMark license comment now correctly says the silhouette is a GitHub Inc. trademark used under the design.md brand-mark exception (was claiming CC-BY 4.0 on GitHub's mark, which doesn't apply). - cwdMatchesProject now normalizes both inputs to forward slash before prefix-checking, so a mixed-separator Windows project path matches its live cwd. Three new regression tests cover the cases. Tests: 81 + 613 green. Clippy --all-targets clean. Fmt clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two structural changes that ride together because both bump the
prompt version stamp on
policy_decisions.is_new=false) or propose new tags withpending_review=truefor staff approval at/admin/flags. Migration 0022 addstags.pending_reviewandsubmission_tags.source('ai'|'user') with a CHECK constraint. Pending tags hide from/cand from public submission rows; staff approve/reject in the new pending-review section.tagSlugSchemainlib/tags/slug.tsis the one canonical slug shape — moderator parsing, admin actions, and the public API now share it.POLICY_CATEGORIESdropsoff_topic. The gate now handles universal trust-and-safety only (spam/abuse/illegal/doxxing). Topical fit is the editorial layer's concern (rubric scoring + voting), not the moderator's. Removes a perpetual drift point for autonomous operation and fixes the misread where gpt-4o-mini was rejecting AI-on-topic content as off-topic-for-AI-audience due to an ambiguous parenthetical in the fallback prompt.POLICY_PROMPT_Vbumped 1→2 sopolicy_decisions.prompt_versionsplits analytics cleanly across the change. Thecategorycolumn is plain text not enum, so legacy rows withcategory='off_topic'stay queryable; display surfaces (/office/policy,/appeal/[id]) keep their off_topic labels for historical rendering.Migration
Migration
0022_ai_tagging.sqladds two columns, a CHECK constraint, and one partial index. Apply manually via psql per.claude/rules/db-migrations.md—drizzle-kit pushagainst prod would drop thesearch_vecgenerated column.Audit + verify
tsc --noEmit→ 0 errorsTest plan
/admin/flags, confirm pending-review section renders any new Ada-proposed tags with sample submission titles/c/<slug>and the moderator vocab cache is cleared (Ada picks it up on next submission)/office/policyshows 4 categories, lede explicitly disclaims topical-fit gatingpolicy_decisions.prompt_versionrows with new submissions are stamped"2"