SESSION_2026-08-29_implementation_comparison.md #1

  • //
  • j2j/
  • dev/
  • ai_dev_support/
  • SESSION_2026-08-29_implementation_comparison.md
  • Markdown
  • View
  • Commits
  • Open Download .zip Download (11 KB)

Session Log: Comparative Review of Two JIRA→P4 Jobs Implementations

Date: 2026-08-29 Author: Claude (Claude Code session) Requested by: Tom Tyler


Task

Two independent AI sessions had each produced a full implementation of a one-way JIRA → P4 Jobs sync tool, developed against similar but independently derived requirements:

  1. Bash — /Users/ttyler/pub/j2j (bin/sync_jira_to_p4jobs.sh), versioned in the Public Depot server at //j2j/dev/.... Target: syncing perforce.atlassian.net SDP issues into Public Depot P4 jobs (workshop.perforce.com) for public visibility.
  2. Python — /Users/ttyler/ppn/user/JIRA2P4Jobs (bin/JIRAtoP4Jobs.py + bin/JIRAtoP4Jobs.sh wrapper), versioned in the PPN server at //user/ttyler/JIRA2P4Jobs/....

Neither had been executed, even in dry-run/no-op mode. Goal: evaluate both against their (explicit or inferred) requirements, decide which to lead with, and identify anything from the other worth folding in — including error handling ideas.


Requirements (stated + inferred)

Both tools converge on the same core contract, which is a good sign it's the right shape for the problem:

  • One-way, read-only mirror. JIRA is always the source of truth. The script must never write to JIRA, and P4 jobs are never authoritative.
  • Incremental. Only poll/process issues updated since some prior point; don't reprocess the whole project every run.
  • Idempotent. Safe to run repeatedly / on a schedule without side effects from re-processing the same issue twice.
  • Fail-safe. Outages (JIRA down, P4 down, auth failure) must not corrupt state or silently skip data — safe to retry.
  • Field mapping. JIRA status/type/summary/description/reporter/dates need translating into the Public Depot jobspec's fields (Status, Type, Project, Severity, ReportedBy, dates, Description).
  • Dry-run capable, since this touches a public-facing production job database and neither tool has ever been run live.

Side-by-side

Dimension Bash (pub/j2j) Python (ppn/.../JIRA2P4Jobs)
Last touched 2026-08-29 (active) 2026-06-06 (stale ~3 months)
JIRA endpoint /rest/api/3/search/jql, nextPageToken (current Atlassian Cloud API) /rest/api/2/search (POST, startAt-style) — Atlassian has been retiring this generation of the search endpoint; real risk this doesn't work against Cloud today
Pagination Yes, loops on nextPageToken None — fixed maxResults=100; issues beyond 100 in the window are silently dropped
Incremental strategy Persistent watermark file, advanced only after a fully clean run; JQL updated >= watermark Fixed rolling window (--hours, default 24h) recomputed fresh every run — a gap of >24h between runs silently drops issues, no persisted state
Concurrency safety flock advisory lock None
Retry/backoff Yes — 429/5xx retried with backoff, 4xx non-retryable, configurable attempts/delay None — any HTTP hiccup is a hard sys.exit
Auth (JIRA) Basic auth, token from a file (JiraTokenFile) Basic auth, token from env var (set by wrapper script from secure/token.value)
Auth (P4) Dedicated service account + stored ticket file, passed via p4 -P <ticket> Ambient — inherits whatever p4 login state / env vars the calling shell already has; script never touches secrets itself
Config External .cfg file (--init scaffolds it), per-project mapping table None — hardcoded JIRA URL, argparse flags for project/hours/execute
Status mapping 10 P4 states, matched on both specific status name and coarse category 3 states only (To Do/In Progress/Done → open/suspended/closed) — and In Progress → suspended looks like a mapping bug (suspended ≠ in-progress)
Type mapping Yes → Bug/Doc/Feature/Problem None
Project field mapping Yes — explicit JiraProjectMap[] config, since the jobspec Project field is an enum unrelated to the JIRA key None — Project is never set on the write, so new jobs get whatever jobspec default applies, not a value derived from JIRA
ReportedBy / dates Set from JIRA reporter/created/updated None — never set; new jobs get p4 defaults (wrong attribution/dates)
Description format ADF (Atlassian Document Format) walked and flattened to plain text Treats description as a raw value; if JIRA Cloud returns ADF (a dict), an f-string will interpolate a Python dict repr into the job — likely broken output for any JIRA Cloud project using rich-text descriptions
Fields preserved on update Explicitly leaves OwnedBy, DevNotes, Component, Release untouched Only touches Job/Description/Status; incidentally preserves everything else, but that's a side effect of doing less, not a deliberate design
Change detection Relies on JIRA-side incremental filter + idempotent overwrite Explicitly fetches the existing job first and diffs Summary/Description/Status before writing — a genuinely nice idea
P4 write mechanism Text form via heredoc + p4 job -i -f p4 -G (Python marshaled dicts) via p4 job -i — structurally safer, no form-syntax edge cases to worry about
Per-item failure handling Logs, counts, continues to next issue; only refuses to advance the watermark Any single job save failure calls sys.exit — aborts the entire run mid-batch
Dry-run default Live by default; -n opts into preview Dry-run by default; --execute opts into live writes
Logging Timestamped log files + stable symlink, verbosity levels 1–5, silent mode for cron print() only
Docs/standards Full -man page, README, follows SDP Bash Coding Standard, ShellCheck findings addressed One paragraph docstring, one small packages note
Dependencies bash, curl, flock, python3 (stdlib only, no jq) python3, requests, (originally p4python, later removed)

Verdict: lead with the Bash implementation

Tom's instinct to favor the Bash script is correct, and not just on stylistic grounds. Beyond fitting the complexity level:

  • It's operationally consistent with SDP, which is otherwise all Bash — no venv/interpreter-version/package management burden on the Public Depot host.
  • It's materially more correct, not just more complete: the Python script has real functional gaps (no Project/Type/ReportedBy/date mapping, no ADF parsing, no pagination, a probably-deprecated JIRA search endpoint) that would need fixing before it could run correctly at all — this isn't a case of "less code, same result," it's a case of "less code, wrong result." Notably, the Python script's staleness (last touched three months ago, never revisited since) also lines up with this — it reads as an earlier, abandoned pass rather than a parallel finished candidate.
  • The Bash script's own author-recorded gaps (ModifiedBy accuracy, Severity hardcoded, Component/OwnedBy unmapped) are pre-existing, disclosed TODOs on an otherwise coherent design — not surprises found in review.

Recommendation: Bash is the lead implementation. Retire/archive the Python one rather than continuing to invest in both.


Ideas worth porting from Python into the Bash lead

  1. Flip the dry-run default. Python defaults to dry-run and requires --execute to write; Bash defaults to live and requires -n to preview. Given this has never been run, safe-by-default is the better posture. Suggest changing Bash so a real run requires an explicit opt-in (e.g. require --execute or -y, keep -n/--noop as an explicit synonym for clarity) rather than making "forgot a flag" the failure mode that writes to a public job database.
  2. Don't put the P4 ticket on the command line. p4_cmd() currently does p4 -P "$P4Passwd" ... — that ticket value is visible to any local user via ps for the life of the process. Python's approach (ambient auth, secret never touches an argv) sidesteps this entirely. For Bash, the cheap fix is exporting P4PASSWD="$P4Passwd" for the p4 subprocess instead of passing -P as an argument. Worth fixing regardless of which tool leads.
  3. Diff-before-write (minor/optional). Python's fetch-existing-job-and- compare-before-saving is a nice pattern to reduce needless writes on a full backfill (InitialLookbackSeconds=0) where every issue in a project gets reprocessed regardless of whether it actually changed. Not urgent — the watermark already limits scope to genuinely-changed issues on normal incremental runs — but worth considering for the initial full-sync path.
  4. Upfront JIRA auth check (minor/optional). Python's explicit /rest/api/*/myself preflight gives an immediate, clear "bad token" error before doing any real work. Bash gets equivalent behavior implicitly (first jira_get call fails non-retryably on 401/403), so this is a nice-to-have for a clearer error message, not a functional gap.

Do not port: Python's JIRA fetch logic (deprecated-looking endpoint, no pagination → silent data loss above 100 issues/window), its 3-value status map (including the apparent In Progress → suspended bug), or its missing Project/Type/ReportedBy/date field mapping. These are regressions relative to the Bash script, not alternatives to weigh.


Other findings worth flagging (independent of which tool leads)

  • Personal API token in use. ppn/.../secure/token.value is Tom's own Atlassian API token (per token.name: ttyler-api-token-expires-2027-06-06), not a dedicated service account. Fine for a first dry run; before any scheduled/unattended production use, create a dedicated read-only JIRA service account so sync attribution/audit trail doesn't depend on a personal credential, and rotation doesn't require touching prod config.
  • p4 job -f requires admin access. This is an inherent Perforce limitation (there's no finer-grained protection just for job date fields), not something either script does wrong — just worth remembering that the service account needs a broad grant, so its ticket file should be tightly permissioned (already is: chmod 600 per the README).
  • First live test path: run the Bash script -n -v 5 against a single project (-p SDP) first, pointed at the already-provisioned token in ppn/.../secure/token.value via JiraTokenFile in the config. Review NoOp output carefully before dropping -n.

Next steps

  1. Apply the two concrete fixes above to the Bash script (safe-by-default dry-run, stop passing the P4 ticket as a CLI argument).
  2. Point JiraTokenFile at the existing PPN token for a first -n -v 5 test run against a single project.
  3. Decide whether to archive or delete the PPN Python implementation, or keep it around only as a reference for the diff-before-write idea.
# Session Log: Comparative Review of Two JIRA→P4 Jobs Implementations

**Date:** 2026-08-29
**Author:** Claude (Claude Code session)
**Requested by:** Tom Tyler

---

## Task

Two independent AI sessions had each produced a full implementation of a
one-way JIRA → P4 Jobs sync tool, developed against similar but independently
derived requirements:

1. **Bash** — `/Users/ttyler/pub/j2j` (`bin/sync_jira_to_p4jobs.sh`), versioned
   in the Public Depot server at `//j2j/dev/...`. Target: syncing
   `perforce.atlassian.net` SDP issues into Public Depot P4 jobs
   (`workshop.perforce.com`) for public visibility.
2. **Python** — `/Users/ttyler/ppn/user/JIRA2P4Jobs` (`bin/JIRAtoP4Jobs.py` +
   `bin/JIRAtoP4Jobs.sh` wrapper), versioned in the PPN server at
   `//user/ttyler/JIRA2P4Jobs/...`.

Neither had been executed, even in dry-run/no-op mode. Goal: evaluate both
against their (explicit or inferred) requirements, decide which to lead with,
and identify anything from the other worth folding in — including error
handling ideas.

---

## Requirements (stated + inferred)

Both tools converge on the same core contract, which is a good sign it's the
right shape for the problem:

- **One-way, read-only mirror.** JIRA is always the source of truth. The
  script must never write to JIRA, and P4 jobs are never authoritative.
- **Incremental.** Only poll/process issues updated since some prior point;
  don't reprocess the whole project every run.
- **Idempotent.** Safe to run repeatedly / on a schedule without side effects
  from re-processing the same issue twice.
- **Fail-safe.** Outages (JIRA down, P4 down, auth failure) must not corrupt
  state or silently skip data — safe to retry.
- **Field mapping.** JIRA status/type/summary/description/reporter/dates need
  translating into the Public Depot jobspec's fields (`Status`, `Type`,
  `Project`, `Severity`, `ReportedBy`, dates, `Description`).
- **Dry-run capable**, since this touches a public-facing production job
  database and neither tool has ever been run live.

---

## Side-by-side

| Dimension | Bash (`pub/j2j`) | Python (`ppn/.../JIRA2P4Jobs`) |
|---|---|---|
| Last touched | 2026-08-29 (active) | 2026-06-06 (stale ~3 months) |
| JIRA endpoint | `/rest/api/3/search/jql`, `nextPageToken` (current Atlassian Cloud API) | `/rest/api/2/search` (POST, `startAt`-style) — Atlassian has been retiring this generation of the search endpoint; **real risk this doesn't work against Cloud today** |
| Pagination | Yes, loops on `nextPageToken` | **None** — fixed `maxResults=100`; issues beyond 100 in the window are silently dropped |
| Incremental strategy | Persistent watermark file, advanced only after a fully clean run; JQL `updated >= watermark` | Fixed rolling window (`--hours`, default 24h) recomputed fresh every run — a gap of >24h between runs silently drops issues, no persisted state |
| Concurrency safety | `flock` advisory lock | None |
| Retry/backoff | Yes — 429/5xx retried with backoff, 4xx non-retryable, configurable attempts/delay | None — any HTTP hiccup is a hard `sys.exit` |
| Auth (JIRA) | Basic auth, token from a file (`JiraTokenFile`) | Basic auth, token from env var (set by wrapper script from `secure/token.value`) |
| Auth (P4) | Dedicated service account + stored ticket file, passed via `p4 -P <ticket>` | Ambient — inherits whatever `p4 login` state / env vars the calling shell already has; script never touches secrets itself |
| Config | External `.cfg` file (`--init` scaffolds it), per-project mapping table | None — hardcoded JIRA URL, `argparse` flags for project/hours/execute |
| Status mapping | 10 P4 states, matched on both specific status name and coarse category | 3 states only (`To Do`/`In Progress`/`Done` → `open`/`suspended`/`closed`) — and `In Progress → suspended` looks like a mapping bug (suspended ≠ in-progress) |
| Type mapping | Yes → `Bug`/`Doc`/`Feature`/`Problem` | **None** |
| Project field mapping | Yes — explicit `JiraProjectMap[]` config, since the jobspec `Project` field is an enum unrelated to the JIRA key | **None** — `Project` is never set on the write, so new jobs get whatever jobspec default applies, not a value derived from JIRA |
| ReportedBy / dates | Set from JIRA `reporter`/`created`/`updated` | **None** — never set; new jobs get p4 defaults (wrong attribution/dates) |
| Description format | ADF (Atlassian Document Format) walked and flattened to plain text | Treats `description` as a raw value; if JIRA Cloud returns ADF (a dict), an f-string will interpolate a Python dict repr into the job — **likely broken output** for any JIRA Cloud project using rich-text descriptions |
| Fields preserved on update | Explicitly leaves `OwnedBy`, `DevNotes`, `Component`, `Release` untouched | Only touches `Job`/`Description`/`Status`; incidentally preserves everything else, but that's a side effect of doing less, not a deliberate design |
| Change detection | Relies on JIRA-side incremental filter + idempotent overwrite | Explicitly fetches the existing job first and diffs Summary/Description/Status before writing — **a genuinely nice idea** |
| P4 write mechanism | Text form via heredoc + `p4 job -i -f` | `p4 -G` (Python marshaled dicts) via `p4 job -i` — structurally safer, no form-syntax edge cases to worry about |
| Per-item failure handling | Logs, counts, continues to next issue; only refuses to advance the watermark | Any single job save failure calls `sys.exit` — **aborts the entire run mid-batch** |
| Dry-run default | Live by default; `-n` opts into preview | **Dry-run by default**; `--execute` opts into live writes |
| Logging | Timestamped log files + stable symlink, verbosity levels 1–5, silent mode for cron | `print()` only |
| Docs/standards | Full `-man` page, README, follows SDP Bash Coding Standard, ShellCheck findings addressed | One paragraph docstring, one small packages note |
| Dependencies | `bash`, `curl`, `flock`, `python3` (stdlib only, no `jq`) | `python3`, `requests`, (originally `p4python`, later removed) |

---

## Verdict: lead with the Bash implementation

Tom's instinct to favor the Bash script is correct, and not just on stylistic
grounds. Beyond fitting the complexity level:

- It's operationally consistent with SDP, which is otherwise all Bash —
  no venv/interpreter-version/package management burden on the Public Depot
  host.
- It's materially more correct, not just more complete: the Python script has
  real functional gaps (no `Project`/`Type`/`ReportedBy`/date mapping, no
  ADF parsing, no pagination, a probably-deprecated JIRA search endpoint) that
  would need fixing before it could run correctly at all — this isn't a case
  of "less code, same result," it's a case of "less code, wrong result."
  Notably, the Python script's staleness (last touched three months ago,
  never revisited since) also lines up with this — it reads as an earlier,
  abandoned pass rather than a parallel finished candidate.
- The Bash script's own author-recorded gaps (`ModifiedBy` accuracy, `Severity`
  hardcoded, `Component`/`OwnedBy` unmapped) are pre-existing, disclosed TODOs
  on an otherwise coherent design — not surprises found in review.

**Recommendation: Bash is the lead implementation.** Retire/archive the Python
one rather than continuing to invest in both.

---

## Ideas worth porting from Python into the Bash lead

1. **Flip the dry-run default.** Python defaults to dry-run and requires
   `--execute` to write; Bash defaults to live and requires `-n` to preview.
   Given this has *never been run*, safe-by-default is the better posture.
   Suggest changing Bash so a real run requires an explicit opt-in (e.g.
   require `--execute` or `-y`, keep `-n`/`--noop` as an explicit synonym for
   clarity) rather than making "forgot a flag" the failure mode that writes to
   a public job database.
2. **Don't put the P4 ticket on the command line.** `p4_cmd()` currently does
   `p4 -P "$P4Passwd" ...` — that ticket value is visible to any local user via
   `ps` for the life of the process. Python's approach (ambient auth, secret
   never touches an argv) sidesteps this entirely. For Bash, the cheap fix is
   exporting `P4PASSWD="$P4Passwd"` for the `p4` subprocess instead of passing
   `-P` as an argument. Worth fixing regardless of which tool leads.
3. **Diff-before-write (minor/optional).** Python's fetch-existing-job-and-
   compare-before-saving is a nice pattern to reduce needless writes on a full
   backfill (`InitialLookbackSeconds=0`) where every issue in a project gets
   reprocessed regardless of whether it actually changed. Not urgent — the
   watermark already limits scope to genuinely-changed issues on normal
   incremental runs — but worth considering for the initial full-sync path.
4. **Upfront JIRA auth check (minor/optional).** Python's explicit
   `/rest/api/*/myself` preflight gives an immediate, clear "bad token" error
   before doing any real work. Bash gets equivalent behavior implicitly (first
   `jira_get` call fails non-retryably on 401/403), so this is a nice-to-have
   for a clearer error message, not a functional gap.

**Do not port:** Python's JIRA fetch logic (deprecated-looking endpoint, no
pagination → silent data loss above 100 issues/window), its 3-value status
map (including the apparent `In Progress → suspended` bug), or its missing
`Project`/`Type`/`ReportedBy`/date field mapping. These are regressions
relative to the Bash script, not alternatives to weigh.

---

## Other findings worth flagging (independent of which tool leads)

- **Personal API token in use.** `ppn/.../secure/token.value` is Tom's own
  Atlassian API token (per `token.name`: `ttyler-api-token-expires-2027-06-06`),
  not a dedicated service account. Fine for a first dry run; before any
  scheduled/unattended production use, create a dedicated read-only JIRA
  service account so sync attribution/audit trail doesn't depend on a
  personal credential, and rotation doesn't require touching prod config.
- **`p4 job -f` requires `admin` access.** This is an inherent Perforce
  limitation (there's no finer-grained protection just for job date fields),
  not something either script does wrong — just worth remembering that the
  service account needs a broad grant, so its ticket file should be tightly
  permissioned (already is: `chmod 600` per the README).
- **First live test path:** run the Bash script `-n -v 5` against a single
  project (`-p SDP`) first, pointed at the already-provisioned token in
  `ppn/.../secure/token.value` via `JiraTokenFile` in the config. Review NoOp
  output carefully before dropping `-n`.

---

## Next steps

1. Apply the two concrete fixes above to the Bash script (safe-by-default
   dry-run, stop passing the P4 ticket as a CLI argument).
2. Point `JiraTokenFile` at the existing PPN token for a first `-n -v 5` test
   run against a single project.
3. Decide whether to archive or delete the PPN Python implementation, or keep
   it around only as a reference for the diff-before-write idea.
# Change User Description Committed
#1 33523 C. Thomas Tyler Renamed folder.
//j2j/dev/ai/SESSION_2026-08-29_implementation_comparison.md
#1 33354 C. Thomas Tyler Safe-by-default dry-run (--execute required to write); pass P4 ticket via P4PASSWD env var instead of -P argv; add .p4ignore for local secure/ token dir; add comparative review session log.