Commit Graph

894 Commits

Author SHA1 Message Date
Hongming Wang
2dbb59cb35 docs: staging environment design + Phase 36 plan
Full staging environment that mirrors production. Every infra change
ships to staging first before promotion. Gates Phase 33 (Tunnel) and
Phase 35 (security hardening).

Components: Railway staging env, Neon branch, staging DNS, tagged
Docker images, promotion workflow, automated smoke tests.

Also marks Phase 33 as migrating from Worker to Cloudflare Tunnel
(issue #933), prerequisite: staging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 20:37:11 -07:00
Hongming Wang
cb122c98e5
Merge pull request #930 from Molecule-AI/fix/ci-path-filter-merge-commits
fix(ci): path filter for merge commits — use event.before
2026-04-17 20:23:44 -07:00
Hongming Wang
7c51e3799c fix(ci): use github.event.before for push diff, fetch-depth 0
HEAD~1 doesn't work for merge commits. Use github.event.before (the
previous main tip) for push events and github.event.pull_request.base.sha
for PRs. fetch-depth: 0 ensures both SHAs are available.

Fallback: if BASE is empty (new branch), run all jobs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 20:23:28 -07:00
Hongming Wang
ce553b5197
Merge pull request #928 from Molecule-AI/fix/ci-path-filter-macos
fix(ci): replace dorny/paths-filter with git diff — unblocks all CI
2026-04-17 20:16:55 -07:00
Hongming Wang
3b5274e712 fix(ci): replace dorny/paths-filter with git diff (macOS compat)
dorny/paths-filter uses Docker internally which doesn't work on the
self-hosted macOS arm64 runner — every CI run since the path filter
change has failed with no jobs.

Replace with a simple git diff against HEAD~1 that checks path prefixes.
Same behavior, no Docker dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 20:16:39 -07:00
Hongming Wang
c56cd3b6b2
Merge pull request #924 from Molecule-AI/docs/session-retrospective-2026-04-17
docs: SaaS buildout retrospective + Phase 35 hardening plan
2026-04-17 20:10:02 -07:00
Hongming Wang
232e90248b docs: session retrospective + Phase 35 hardening plan
Full retrospective of the 2026-04-16/17 SaaS buildout session:
- What was done (infra migration, 40+ PRs, 5 issues, 4 docs, 1 new repo)
- What should NOT have been changed (wildcard DNS churn, AdminAuth shortcut)
- Security concerns (8 items, 2 CRITICAL)
- Workflow gaps (registration, boot time, CI)
- Tests needed (automated + manual + security)

Phase 35 in PLAN.md covers production hardening follow-ups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 20:08:39 -07:00
rabbitblood
06252723f3 fix: auto-post only to Slack, never Telegram
BroadcastToWorkspaceChannels now filters channel_type='slack'.
Telegram is CEO-only — explicit escalations via agent's outbound call,
never auto-posted from cron output. PM's routine pulses and agent
errors were spamming the CEO's Telegram.

PM's Telegram channel stays enabled for POLLING (inbound CEO messages)
but BroadcastToWorkspaceChannels skips it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 19:04:31 -07:00
molecule-ai[bot]
e4136d6b2a
Merge pull request #891 from Molecule-AI/fix/issue-826-smol-executor-env-sanitization
feat(security): denylist env sanitization + safe messaging for smolagents
2026-04-18 01:44:26 +00:00
molecule-ai[bot]
a5ebd49caf
Merge pull request #873 from Molecule-AI/fix/issue-854-eject-tooltip
fix(canvas): restore title tooltip on TeamMemberChip eject button alongside aria-label
2026-04-18 01:43:32 +00:00
7c49e0c86a Merge branch 'main' of https://github.com/Molecule-AI/molecule-core into fix/issue-854-eject-tooltip
# Conflicts:
#	canvas/src/components/WorkspaceNode.tsx
2026-04-18 01:43:00 +00:00
molecule-ai[bot]
776e7a50eb
Merge pull request #802 from Molecule-AI/chore/eco-watch-2026-04-17-i
chore(eco-watch): smolagents WATCH verdict + Managed Agents entry — 2026-04-17 run i
2026-04-18 01:34:57 +00:00
molecule-ai[bot]
ad4a210a16
Merge pull request #906 from Molecule-AI/fix/a11y-audit-902-905
fix(a11y): resolve accessibility issues #902–#905 (aria-pressed, aria-expanded, alertdialog, ID sanitisation)
2026-04-18 01:34:47 +00:00
80fceea243 fix(gate-6): merge main into fix/a11y-audit-902-905 — resolve 7 conflicts
Conflicts arose because PR #892 base commits (MemoryInspectorPanel creation,
A2A overlay) had already landed on main via a different merge path, and
last-tick merges (#876, #888) had modified Toolbar, SidePanel, and test
fixtures.

Resolution strategy:
- Toolbar.tsx, SidePanel.tsx, Canvas.a11y.test.tsx, Canvas.pan-to-node.test.tsx,
  MemoryInspectorPanel.test.tsx: take main (strictly newer, already contains
  the branch's A2A overlay content plus subsequent a11y/UX fixes)
- MemoryInspectorPanel.tsx: take main (543 lines with semantic search) + apply
  sanitizeId() helper from #904 + update bodyId prefix to mem-body-
- DetailsTab.tsx: take main (has #875 Field/useId + #878 deleteButtonRef/focus)
  + apply alertdialog structure from #905 while preserving focus management

Mechanical conflict resolution by triage-agent; no logic changes beyond the
four a11y fixes already in the branch (#902-#905).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 01:34:00 +00:00
molecule-ai[bot]
31779e99eb
Merge pull request #875 from Molecule-AI/fix/canvas-a11y-configtab-detailstab-htmlfor
fix(canvas): htmlFor/id association in ConfigTab + DetailsTab inputs
2026-04-18 01:24:42 +00:00
a696d7f235 Merge branch 'main' of https://github.com/Molecule-AI/molecule-core into fix/canvas-a11y-configtab-detailstab-htmlfor
# Conflicts:
#	canvas/src/components/tabs/DetailsTab.tsx
2026-04-18 01:24:15 +00:00
888353891e fix(gate-6): reconcile DetailsTab.tsx import — merge useRef (#878) with useId/cloneElement (#875)
PR #878 landed before this branch and added useRef + deleteButtonRef focus-
management to DetailsTab.tsx. This commit combines that import with the
useId/cloneElement import added here, and preserves the Field component
htmlFor/id wiring from this PR unchanged.

Mechanical conflict resolution by triage-agent; no logic changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 01:22:08 +00:00
molecule-ai[bot]
f159f149a0
Merge pull request #888 from Molecule-AI/fix/canvas-a11y-sidepanel-resize-keyboard
fix(canvas): a11y — SidePanel keyboard resize, MemoryEntryRow aria-controls, contrast + ChatTab error banner
2026-04-18 01:20:02 +00:00
molecule-ai[bot]
dcdd851c68
Merge pull request #887 from Molecule-AI/fix/canvas-a11y-conversation-trace-modal
fix(canvas): a11y — migrate ConversationTraceModal to Radix Dialog with aria-label
2026-04-18 01:19:57 +00:00
molecule-ai[bot]
ec515ba755
Merge pull request #878 from Molecule-AI/fix/canvas-a11y-detailstab-delete-confirm
fix(canvas): a11y improvements to DetailsTab delete confirmation dialog
2026-04-18 01:19:52 +00:00
molecule-ai[bot]
2844109078
Merge pull request #877 from Molecule-AI/fix/canvas-a11y-emptystate-role-alert
fix(canvas): add role=alert to empty-state error messages
2026-04-18 01:19:48 +00:00
molecule-ai[bot]
973cf15f7d
Merge pull request #876 from Molecule-AI/fix/canvas-a11y-toolbar-aria-label
fix(canvas): add aria-label to Toolbar icon buttons
2026-04-18 01:19:44 +00:00
molecule-ai[bot]
7b1833658f
Merge pull request #874 from Molecule-AI/fix/canvas-a11y-onboarding-aria-live
fix(canvas): add aria-live region to onboarding step transitions
2026-04-18 01:19:36 +00:00
Hongming Wang
be4e3bb485
Merge pull request #900 from Molecule-AI/fix/ci-go-mod-replace
fix(ci): remove go.mod replace /plugin — unblocks all CI
2026-04-17 18:17:11 -07:00
Molecule AI Frontend Engineer
1e4b6d0203 fix(a11y): DetailsTab — use role=alertdialog for delete confirmation (#905)
role="alert" is for passive announcements. A delete confirmation with
Confirm/Cancel action buttons requires a user response, which is the
semantics of role="alertdialog" (interactive dialog requiring response).

- Replace role="alert" with role="alertdialog" + aria-modal="true"
- Add aria-labelledby="delete-confirm-title" for an accessible name
- Add <h3 id="delete-confirm-title"> as the labelling element
  ("Confirm deletion") so AT announces the dialog purpose on focus

Closes #905

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 01:14:51 +00:00
Molecule AI Frontend Engineer
33cae9bb94 fix(a11y): MemoryInspectorPanel — sanitise bodyId, add aria-controls (#904)
Memory keys can contain characters like [ ] / : . # and spaces that make
invalid HTML id values (breaks CSS selectors and ARIA id-ref lookups).

- Add sanitizeId() helper: replaces non-alphanumeric chars with hyphens,
  collapses consecutive hyphens, strips leading/trailing hyphens
- Compute bodyId = "mem-body-{sanitizeId(entry.key)}" in MemoryEntryRow
- Set id={bodyId} on the expanded body container
- Set aria-controls={bodyId} on the toggle button so AT can navigate
  directly between the button and its controlled panel

Closes #904

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 01:14:35 +00:00
Molecule AI Frontend Engineer
92f95255c7 fix(a11y): ActivityTab — aria-pressed on filter pills and auto-refresh (#903)
- Add aria-pressed={filter === f.id} to every filter pill button so AT
  announces which filter is currently active
- Add aria-pressed={autoRefresh} to the auto-refresh toggle so AT
  announces the live/paused state when the button is activated

Closes #903

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 01:14:10 +00:00
Molecule AI Frontend Engineer
403fe63db8 fix(a11y): MemoryTab — role=alert, labelled inputs, aria-expanded (#902)
- Add role="alert" to the global error banner and the inline add-form
  error message so screen readers announce errors immediately on render
- Add aria-label to all three add-form inputs (key / value / TTL) so
  every form control has an accessible name (was flagged as unlabelled)
- Add aria-expanded={expanded === entry.key} to each entry toggle button
  so AT announces collapsed/expanded state on activation

Closes #902

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 01:13:56 +00:00
Molecule AI Frontend Engineer
b5d85a4706 fix(a11y): add role=alert to MemoryInspectorPanel error banner (#901)
The error banner div introduced in the MemoryInspectorPanel (PR #892)
was missing role="alert", regressing the a11y standard established in
PR #877 / issue #830. Screen readers now announce the error immediately
on render.

Closes #901

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 01:12:01 +00:00
Hongming Wang
a891bf9b4b fix(ci): remove go.mod replace /plugin — add it at Docker build time only
The replace directive `=> /plugin` breaks CI builds where go build runs
natively (no /plugin directory). Move the replace to Dockerfile RUN so
it only applies during Docker builds where the plugin is COPYed.

Fixes: "replacement directory /plugin does not exist" on CI runner.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 18:08:53 -07:00
rabbitblood
f7706051aa fix: strip CRLF in entrypoint.sh at every container start
Windows Docker Desktop copies host files with CRLF even when
.gitattributes says eol=lf. The entrypoint now strips \r from all
hook .sh/.py files before dropping to agent user. Permanent fix for
the #507 CRLF regression that reappeared after every restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 18:06:04 -07:00
rabbitblood
43878d5b8f Merge branch 'main' of https://github.com/Molecule-AI/molecule-core 2026-04-17 17:52:18 -07:00
rabbitblood
92d80c0ee4 feat(telegram): poll for callback_query — CEO decision buttons work locally
Adds callback_query to AllowedUpdates in Telegram polling. When CEO
clicks Yes/No inline keyboard buttons:
1. Acknowledges press (removes loading spinner)
2. Updates message with 'CEO approved/rejected'
3. Routes 'CEO_DECISION: approve:xyz' as inbound to the agent

Only one workspace polls per bot token (Triage Operator) — other
workspaces with Telegram use outbound-only via direct API.

Fixed: duplicate pollers causing 'terminated by other getUpdates'
errors — removed PM/DevLead/ResearchLead Telegram channel rows
(they send outbound via direct Telegram API calls, not channel manager).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 17:52:10 -07:00
Molecule AI Frontend Engineer
67799f89da fix(canvas): resolve TS errors in test fixtures — budgetLimit and AuthGate mock types
- Add budgetLimit: null to WorkspaceNodeData fixtures in canvas-capabilities,
  canvas-events, canvas-events-pan, and canvas.test.ts (inline objects)
- Add budget_limit: null to WorkspaceData fixtures in canvas-topology,
  canvas.test.ts makeWS, and ProvisioningTimeout.test.tsx
- Fix AuthGate.test.tsx TS2348: cast vi.fn() mocks to explicit call
  signatures inside vi.mock() factories (Procedure | Constructable issue)
- npx tsc --noEmit: 0 errors; 689/689 tests passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 00:43:55 +00:00
Hongming Wang
a7e0ac7912
Merge pull request #881 from Molecule-AI/fix/issue-838-memory-secret-redact
fix(security): SAFE-T1201 — redact secrets in commit_memory before persistence (#838)
2026-04-17 17:17:19 -07:00
Hongming Wang
006c8f49c8
Merge pull request #882 from Molecule-AI/fix/issue-819-hibernate-toctou
fix(platform): atomic hibernate — TOCTOU race in HibernateWorkspace (closes #819)
2026-04-17 17:17:16 -07:00
Molecule AI Research Lead
7d905d5089 chore(eco-watch): smolagents WATCH → BUILD (threshold override, PM auth)
26,688★ below 30k criterion — BUILD authorized: HF corporate backing,
Tool.from_langchain zero-cost integration (~145 LOC), ~60-day trajectory
to 30k. Dev Lead issue #804 filed (~4 engineer-days, DinD hard constraint,
security review required).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 00:16:39 +00:00
Molecule AI Research Lead
9ff0d85684 chore(eco-watch): update smolagents WATCH verdict + add Managed Agents — 2026-04-17 run i
smolagents (GH #792 closed): WATCH — 2/3 criteria pass. A2A shim ~120-160 LOC
(fastapi-agents pattern validated), Apache-2.0 no lock-in, but 26.5k★ < 30k
threshold. Re-evaluate at 30k★ (~4-6 weeks) or HF default designation.
DinD gotcha documented: use local/e2b executor_type inside workspace containers.

Anthropic Managed Agents (GH #742 closed): WATCH-FOR-GA — beta API unstable,
RBAC passthrough requires async sidecar (architecturally non-trivial), cost
neutral at ~2 active hrs/day, session checkpointing ≠ Temporal replacement.
Re-evaluate at GA + multiagent research-preview exit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 00:16:39 +00:00
Molecule AI Research Lead
6d5fd8bb9a chore(eco-watch): add smolagents — 2026-04-17
Hugging Face's code-first agent framework (26.5k★, Apache-2.0). CodeAgent
pattern (Python-native tool calls), LiteLLM model-agnostic, E2B/Docker
sandboxing, Hub tool registry. Filed GH #792 to evaluate
molecule-ai-workspace-template-smolagents adapter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 00:16:39 +00:00
molecule-ai[bot]
bcd256946f
Merge pull request #890 from Molecule-AI/test/issue-790-crash-resume-integration
test(integration): crash-resume integration tests for Temporal checkpoints (#790)
2026-04-18 00:02:48 +00:00
molecule-ai[bot]
159c90e0f5
Merge pull request #798 from Molecule-AI/feat/issue-499-clean-3
feat(hermes): stacked system messages — persona + tools + reasoning policy (#499)
2026-04-18 00:02:29 +00:00
Molecule AI Backend Engineer
228d119e88 feat(security): denylist env sanitization + safe messaging for smolagents (#826, #827)
Add safe_env.py (denylist-based make_safe_env), send_message_wrapper.py
(label prefix, 2000-char cap, HTML entity escaping) and 33 pytest tests
covering all four security properties. Update __init__.py to re-export
safe_send_message alongside the existing allowlist-based make_safe_env.

Closes #826, closes #827

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:57:59 +00:00
Molecule AI Backend Engineer
9d171bda7f feat(hermes): stacked system messages — persona + tools + reasoning policy (#499)
HermesA2AExecutor now supports sending system context as ordered, separate
role=system messages instead of a single concatenated string — the model
format recommended by NousResearch.

Changes:
- HermesA2AExecutor.__init__: new system_blocks kwarg (list[str|None]|None)
  stored as an independent copy; None blocks and empty strings silently skipped
- _build_messages(): when system_blocks is not None, emits each non-empty
  block as a separate {"role": "system"} entry in Hermes-recommended order
  (persona → tools context → reasoning policy); falls through to legacy
  system_prompt path when system_blocks is None (backward compatible)

Backward compatibility: existing callers that pass a single system_prompt
string continue to work identically — no changes required.

Tests (12 new, 47 total):
  - system_blocks stored as independent copy (mutation safe)
  - three-block stacked ordering preserved
  - empty / None blocks silently skipped
  - all-empty list → zero system messages
  - system_blocks overrides system_prompt when both provided
  - legacy system_prompt path unchanged
  - stacked blocks appear in the live API call kwargs

Closes #499

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:53:12 +00:00
rabbitblood
fe250b256b fix: restore plugin COPY in Dockerfile — permanently fixes token endpoint
The Dockerfile COPY for molecule-ai-plugin-github-app-auth was lost
during a rebase earlier this session. Without it, the platform binary
compiled without the TokenProvider interface implementation, causing
/admin/github-installation-token to return 'no token provider registered'.

This forced hourly rolling restarts to refresh GH_TOKEN (the env var
from provision time expires after ~60 min). Each restart also required
re-applying 6 manual patches and caused ~2 min of A2A downtime where
agents reported peers as 'unresponsive'.

With this fix, the gh-wrapper in each container auto-refreshes tokens
via the platform endpoint on every gh call. Zero restarts needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:47:30 -07:00
Molecule AI Backend Engineer
4b8f4108cd fix(security): SAFE-T1201 — redact secrets in commit_memory before persistence
Adds `redactSecrets()` to the MemoriesHandler, scrubbing known credential
patterns before every INSERT into agent_memories, regardless of scope.

Closes #838. Satisfies SAFE-T1201 gate.

Patterns redacted (with `[REDACTED:<CLASS>]` replacement):
- Env-var assignments: `*_API_KEY=`, `*_TOKEN=`, `*_SECRET=`
- HTTP Bearer tokens
- sk-... prefixed keys (OpenAI / Anthropic format)
- ctx7_... tokens (context7)
- Base64 blobs ≥ 33 chars

The audit log SHA-256 hash now reflects the sanitised content (not the
raw input) so the forensic trail remains consistent with what was stored.

Tests added:
- TestRedactSecrets_CleanContent_PassesThrough
- TestRedactSecrets_APIKeyPattern_IsRedacted (API_KEY / TOKEN / SECRET)
- TestRedactSecrets_BearerToken_IsRedacted
- TestRedactSecrets_SKToken_IsRedacted
- TestRedactSecrets_Ctx7Token_IsRedacted
- TestRedactSecrets_Base64Blob_IsRedacted
- TestCommitMemory_SecretInContent_IsRedactedBeforeInsert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:38:57 +00:00
Molecule AI Frontend Engineer
4acc6c1ed2 fix(a11y): add aria-label to Dialog.Content in ConversationTraceModal (Issue M)
Per UIUX Cycle 5 spec, Dialog.Content should carry an explicit
aria-label="Conversation trace" in addition to the aria-labelledby
automatically wired by Radix Dialog via Dialog.Title. This provides
a fallback accessible name directly on the dialog container element.

All 732 tests pass, build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:31:20 +00:00
Molecule AI Frontend Engineer
68ad062ae8 fix(a11y): migrate ConversationTraceModal to Radix Dialog (Issue M)
Custom <div> modal lacked focus trap, Escape handling, aria-modal, and
aria-labelledby. Migrated to the codebase-standard Radix Dialog pattern
(same as CreateWorkspaceDialog and SettingsPanel) which provides all
required WCAG 2.1 modal semantics automatically:

  • Dialog.Root + Dialog.Portal + Dialog.Overlay + Dialog.Content
    → role="dialog", aria-labelledby, focus trap, Escape key
  • Dialog.Title wraps "Conversation Trace" heading
    → aria-labelledby points to the title element
  • Dialog.Close asChild on ✕ button with aria-label="Close conversation trace"
    → accessible name for the dismiss button (WCAG 4.1.2)
  • Dialog.Close asChild on footer Close button
  • Backdrop → Dialog.Overlay (z-[59]) + Content wrapper (z-[60])
  • All timeline/body content unchanged; only modal scaffolding replaced

Added 10 WCAG tests in ConversationTraceModal.a11y.test.tsx covering:
dialog presence, accessible name, aria-labelledby, data-state, ✕ button
aria-label, close button click, Escape key, and loading indicator. All
732 tests pass, build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:26:47 +00:00
rabbitblood
cbee9a7237 chore: extract molecule-medo plugin to standalone repo
molecule-medo now lives at Molecule-AI/molecule-ai-plugin-molecule-medo
(same pattern as all other plugins). Removed the gitignore exception
that kept it in the monorepo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:11:50 -07:00
rabbitblood
595aa3681d chore: move spike/ → docs/spikes/ — keep explorations out of repo root
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:09:12 -07:00
Molecule AI Frontend Engineer
b57a8fa62b fix(canvas): align SkillsTab aria-label with spec — "Install from source URL"
Corrects the source-input aria-label wording to match the UIUX Cycle 4
spec exactly. Previous commit used "Install plugin from source URL";
spec says "Install from source URL" (matches the visible "Install from
source" section heading). Updates the corresponding test assertions.

No functional change. All 736 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 23:06:21 +00:00