Sync work-record index
Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 6m2s
Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 6m2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 1370019@bnt-lap001 Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
This commit is contained in:
parent
733c707dc5
commit
df18df3377
7 changed files with 96 additions and 19 deletions
|
|
@ -23,6 +23,7 @@
|
|||
| workplan | CSOC-WP-0013 | ready | — | workplans/CSOC-WP-0013-sharing-content-hardening.md |
|
||||
| workplan | CSOC-WP-0014 | active | — | workplans/CSOC-WP-0014-cohort-transfer-bubble-offramp.md |
|
||||
| workplan | CSOC-WP-0015 | active | — | workplans/CSOC-WP-0015-communication-runtime-acceptance.md |
|
||||
| workplan | CSOC-WP-0016 | ready | — | workplans/CSOC-WP-0016-mfa-setup-link.md |
|
||||
| task | CSOC-WP-0001-T01 | done | — | workplans/CSOC-WP-0001-bubble-io-exit-assessment.md |
|
||||
| task | CSOC-WP-0001-T02 | done | — | workplans/CSOC-WP-0001-bubble-io-exit-assessment.md |
|
||||
| task | CSOC-WP-0001-T03 | done | — | workplans/CSOC-WP-0001-bubble-io-exit-assessment.md |
|
||||
|
|
@ -97,6 +98,7 @@
|
|||
| task | CSOC-WP-0015-T02 | todo | — | workplans/CSOC-WP-0015-communication-runtime-acceptance.md |
|
||||
| task | CSOC-WP-0015-T03 | wait | — | workplans/CSOC-WP-0015-communication-runtime-acceptance.md |
|
||||
| task | CSOC-WP-0015-T04 | todo | — | workplans/CSOC-WP-0015-communication-runtime-acceptance.md |
|
||||
| task | CSOC-WP-0016-T01 | todo | — | workplans/CSOC-WP-0016-mfa-setup-link.md |
|
||||
| intake | CSOC-IN-0001 | closed | blue | docs/intakes/csoc-residuals.md |
|
||||
| intake | CSOC-IN-0002 | closed | green | docs/intakes/csoc-residuals.md |
|
||||
| intake | CSOC-IN-0003 | open | blue | docs/intakes/csoc-residuals.md |
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
from django.contrib import admin
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError
|
||||
|
||||
from . import pageops
|
||||
from .models import Space, SpaceMembership
|
||||
|
|
@ -15,7 +17,12 @@ class SpaceAdmin(admin.ModelAdmin):
|
|||
def save_model(self, request, obj, form, change):
|
||||
with pageops.content_write_lock():
|
||||
obj.full_clean()
|
||||
super().save_model(request, obj, form, change)
|
||||
try:
|
||||
super().save_model(request, obj, form, change)
|
||||
except IntegrityError as exc:
|
||||
raise ValidationError(
|
||||
{"slug": "This content instance already uses that slug"}
|
||||
) from exc
|
||||
|
||||
list_display = (
|
||||
"title",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import tempfile
|
|||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from django.db import transaction
|
||||
from django.db import IntegrityError, transaction
|
||||
|
||||
from coulomb_social.apps.members.models import Member
|
||||
|
||||
|
|
@ -162,12 +162,17 @@ def _import_space_tree(
|
|||
published = False
|
||||
try:
|
||||
with transaction.atomic():
|
||||
space, created = Space.objects.update_or_create(
|
||||
tenant_id=tenant_id, slug=space_slug,
|
||||
defaults={"title": display_title, "description": abstractor,
|
||||
"visual": visual, "is_active": True,
|
||||
"content_root": "pages", "default_branch": "main"},
|
||||
)
|
||||
try:
|
||||
space, created = Space.objects.update_or_create(
|
||||
tenant_id=tenant_id, slug=space_slug,
|
||||
defaults={"title": display_title, "description": abstractor,
|
||||
"visual": visual, "is_active": True,
|
||||
"content_root": "pages", "default_branch": "main"},
|
||||
)
|
||||
except IntegrityError as exc:
|
||||
raise pageops.PageOpsError(
|
||||
"Content slug belongs to another tenant"
|
||||
) from exc
|
||||
report.space_created = created
|
||||
report.space_updated = not created
|
||||
for mapped, role in grants:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
"""Enforce one content slug per instance.
|
||||
|
||||
Forward refuses while any slug, including an inactive row, is stored more than
|
||||
once. It does not rename, merge, or delete those rows. Reconcile them first,
|
||||
then migrate again.
|
||||
|
||||
Rollback is `manage.py migrate spaces 0002`. That drops
|
||||
`spaces_space_slug_global_uniq` only. `spaces_space_tenant_slug_uniq` stays,
|
||||
and no content or membership rows are rewritten. After rollback, application
|
||||
checks still reject ordinary collisions; a restored legacy duplicate is denied
|
||||
on read until the rows are reconciled and this migration is applied again.
|
||||
"""
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.db.models import Count
|
||||
|
||||
|
||||
def refuse_legacy_slug_collisions(apps, schema_editor):
|
||||
Space = apps.get_model("spaces", "Space")
|
||||
duplicates = list(
|
||||
Space.objects.values("slug")
|
||||
.annotate(n=Count("pk"))
|
||||
.filter(n__gt=1)
|
||||
.order_by("slug")
|
||||
)
|
||||
if duplicates:
|
||||
listed = ", ".join(row["slug"] for row in duplicates)
|
||||
raise RuntimeError(
|
||||
"Refusing spaces_space_slug_global_uniq: slug-keyed content already "
|
||||
f"has duplicate rows ({listed}). Reconcile or discontinue those rows "
|
||||
"before migrating. This migration does not rename, merge, or delete them."
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("spaces", "0002_space_visual"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(refuse_legacy_slug_collisions, migrations.RunPython.noop),
|
||||
migrations.AddConstraint(
|
||||
model_name="space",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("slug",),
|
||||
name="spaces_space_slug_global_uniq",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -75,6 +75,13 @@ class Space(models.Model):
|
|||
fields=["tenant_id", "slug"],
|
||||
name="spaces_space_tenant_slug_uniq",
|
||||
),
|
||||
# Content paths are spaces/<slug>/, shared by every tenant on this
|
||||
# instance. Inactive rows keep the slug so a later space cannot
|
||||
# reuse an archived tree.
|
||||
models.UniqueConstraint(
|
||||
fields=["slug"],
|
||||
name="spaces_space_slug_global_uniq",
|
||||
),
|
||||
]
|
||||
indexes = [
|
||||
models.Index(fields=["tenant_id", "is_active"]),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import Count, Q, QuerySet
|
||||
|
||||
from coulomb_social.apps.members.models import Member
|
||||
|
|
@ -111,16 +111,21 @@ def create_space(
|
|||
if Space.objects.filter(slug=space_slug).exists():
|
||||
raise pageops.PageOpsError("A space with this slug already exists in this content instance")
|
||||
|
||||
space = Space.objects.create(
|
||||
tenant_id=member.tenant_id,
|
||||
slug=space_slug,
|
||||
title=title,
|
||||
description=abstractor or "",
|
||||
visual=visual or "",
|
||||
created_by=member,
|
||||
content_root="pages",
|
||||
default_branch="main",
|
||||
)
|
||||
try:
|
||||
space = Space.objects.create(
|
||||
tenant_id=member.tenant_id,
|
||||
slug=space_slug,
|
||||
title=title,
|
||||
description=abstractor or "",
|
||||
visual=visual or "",
|
||||
created_by=member,
|
||||
content_root="pages",
|
||||
default_branch="main",
|
||||
)
|
||||
except IntegrityError as exc:
|
||||
raise pageops.PageOpsError(
|
||||
"A space with this slug already exists in this content instance"
|
||||
) from exc
|
||||
SpaceMembership.objects.get_or_create(
|
||||
space=space,
|
||||
member=member,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ created: "2026-10-11"
|
|||
updated: "2026-10-11"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "bf60ed62-5cbb-5276-80da-6c5dec302a2e"
|
||||
---
|
||||
|
||||
Founder finding F1 from the attended MFA run of 2026-10-11 (user-engine
|
||||
|
|
@ -24,6 +25,7 @@ app.
|
|||
id: CSOC-WP-0016-T01
|
||||
status: todo
|
||||
priority: low
|
||||
state_hub_task_id: "b8bb27c0-de32-597e-9106-c5657d7ab130"
|
||||
```
|
||||
|
||||
On the signed-in user's session/account details page, add a "Manage authenticator app (optional)"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue