From 74dd61db7df49ad2aeeeafc19b106cd5946b6be3 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Wed, 11 Nov 2026 21:41:20 +0800 Subject: [PATCH 1/5] feat!: declare models by overriding accessors, not by dunder Breaking, following rhosocial-activerecord: model fixtures and examples now override table_name(), primary_key(), pk_auto_generated(), read_only() and the locator methods instead of assigning __table_name__ and friends. The framework stopped reading those dunders, so a model still declaring them would fail with NotImplementedError at the first read. Produced by the core repository's declaration sweep (scripts/ d5_migrate_declarations.py), so every repository in the family carries the same shape. No new lint findings: the totals drop where the removed assignments took their imports with them. CI for this branch installs the family from the matching feature/declaration-surface ref, so a run exercises one revision across core, testsuite and the backend under test rather than a mixture of two. --- .github/workflows/test.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0ad39791..24c4b919 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,8 +63,8 @@ jobs: - name: Install activerecord and testsuite run: | - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/error-taxonomy" # NOSONAR - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/error-taxonomy" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR - name: Install dependencies run: | @@ -278,8 +278,8 @@ jobs: - name: Install activerecord and testsuite run: | - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/error-taxonomy" # NOSONAR - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/error-taxonomy" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR - name: Install dependencies run: | @@ -385,8 +385,8 @@ jobs: - name: Install activerecord and testsuite run: | - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/error-taxonomy" # NOSONAR - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/error-taxonomy" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR - name: Install dependencies run: | @@ -488,8 +488,8 @@ jobs: - name: Install activerecord and testsuite run: | - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/error-taxonomy" # NOSONAR - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/error-taxonomy" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR - name: Install dependencies run: | From 717b9790e283960dc79f3c917c2370a066658245 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Thu, 12 Nov 2026 13:30:14 +0800 Subject: [PATCH 2/5] fix(introspection): name the probe rule and stop claiming it may return None The helper that supplies the census' arguments was called _synthesize_arguments: "synthesize" names the act of producing something, not what is produced, for whom, or by what rule -- a reader at the call site cannot say what will be passed. Its rule is concrete and the module already says "probe" everywhere else (PROBE_TABLE, PROBE_COLUMN, PROBE_OBJECT), so the name now says it: _probe_arguments_for. Its return annotation was dict | None, which was wrong twice. Nothing in the function returns None -- every branch fills kwargs and the only return is that dict -- so the annotation misdescribed the contract, and evaluated at definition time it also made the module unimportable on Python 3.8 and 3.9 (PEP 604 arrived in 3.10). It is Dict[str, Any] now, which states what the function does and imports on every version this package supports. The assertion message and the docstring follow the new name, so a reader who trips the "un-askable family" guard is pointed at a helper they can recognise. --- .../test_introspection_family_census.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/introspection/test_introspection_family_census.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/introspection/test_introspection_family_census.py index bf4d594e..e03fd957 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/introspection/test_introspection_family_census.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/introspection/test_introspection_family_census.py @@ -37,6 +37,7 @@ """ import inspect from collections import defaultdict +from typing import Any, Dict from rhosocial.activerecord.backend.introspection import IntrospectionNotSupportedError @@ -52,8 +53,8 @@ PROBE_LIMIT = 5 -def _synthesize_arguments(method) -> dict | None: - """Build the call's arguments from the signature. +def _probe_arguments_for(method) -> Dict[str, Any]: + """Choose a probe value for every parameter of *method*. A recognized parameter gets the probe value. One no rule covers keeps its default — or None when it has none — because `list_tables(include_system= @@ -122,7 +123,7 @@ def test_every_family_answers_or_refuses_by_name(postgres_backend): method = getattr(introspector, name) if not callable(method): continue - kwargs = _synthesize_arguments(method) + kwargs = _probe_arguments_for(method) if kwargs is None: skipped.append(name) continue @@ -142,11 +143,11 @@ def test_every_family_answers_or_refuses_by_name(postgres_backend): answered.append(name) # Nothing may be silently un-askable. A parameter this walk cannot - # synthesize is a gap in the walk, not a gap in the backend — it is reported + # name a probe value for is a gap in the walk, not a gap in the backend — it is reported # so the next contract change makes the walk grow rather than skip. assert not skipped, ( - "these families were not asked because their parameters could not be " - "synthesized; extend _synthesize_arguments rather than leaving them " + "these families were not asked because no probe value names their " + "parameters; extend _probe_arguments_for rather than leaving them " f"unvisited: {sorted(skipped)}" ) @@ -180,7 +181,7 @@ def test_argument_synthesis_asks_the_smallest_question(): def smallest(schema=None, table_name="", column_name="", limit=0, name=""): pass - assert _synthesize_arguments(smallest) == { + assert _probe_arguments_for(smallest) == { "schema": None, "table_name": PROBE_TABLE, "column_name": PROBE_COLUMN, @@ -195,7 +196,7 @@ def unaskable(scope, include_system=False): # family: the read still has to parse. A required parameter with no default # answers None — an absence the server can read, rather than a value the # rules would have had to invent. - assert _synthesize_arguments(unaskable) == { + assert _probe_arguments_for(unaskable) == { "scope": None, "include_system": False, } @@ -203,7 +204,7 @@ def unaskable(scope, include_system=False): def required_and_unknown(scope, table_name): pass - assert _synthesize_arguments(required_and_unknown) == { + assert _probe_arguments_for(required_and_unknown) == { "scope": None, "table_name": PROBE_TABLE, } @@ -214,7 +215,7 @@ def optional_everything(include_system=False, table_name=""): # Optional parameters are asked too: the contract's `schema=None` precedes # `table_name=""` in most signatures, so skipping defaults would ask nothing # at all — the census would pass by never asking a question. - assert _synthesize_arguments(optional_everything) == { + assert _probe_arguments_for(optional_everything) == { "include_system": False, "table_name": PROBE_TABLE, } @@ -222,7 +223,7 @@ def optional_everything(include_system=False, table_name=""): def all_recognizable(schema=None, table_name="", column_name="", limit=0, name=""): pass - assert _synthesize_arguments(all_recognizable) == { + assert _probe_arguments_for(all_recognizable) == { "schema": None, "table_name": PROBE_TABLE, "column_name": PROBE_COLUMN, From 618e5fed710f4ac701f83c731abd291bc08089b6 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Thu, 12 Nov 2026 13:39:45 +0800 Subject: [PATCH 3/5] test(introspection): recognise the dialect gate as a refusal, not a server error The census accepts two answers: a family that answers, and a family that refuses by a typed exception. It only recognised IntrospectionNotSupportedError, so on PostgreSQL 9 the partition family -- which is gated by the dialect with UnsupportedFeatureError before any statement is sent -- was recorded as "a catalog statement this release does not serve". That reading is wrong twice: the statement was never sent, and the refusal is the contract working as designed. Both classes are now accepted and the group key still separates them, because each names which layer refused: the dialect for a capability the server does not have, the introspector for a family it does not serve. The walk's "un-askable family" guard is gone with it. _probe_arguments_ for always returns a mapping -- an uncovered parameter keeps its own default -- so the guard could never fire, and a check that cannot fail claims protection it does not give. The docstring now says what the code does: every family is asked, and a family that answers nothing is a fact about the backend rather than about the walk. --- .../test_introspection_family_census.py | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/tests/rhosocial/activerecord_postgres_test/feature/backend/introspection/test_introspection_family_census.py b/tests/rhosocial/activerecord_postgres_test/feature/backend/introspection/test_introspection_family_census.py index e03fd957..d0a3b7cb 100644 --- a/tests/rhosocial/activerecord_postgres_test/feature/backend/introspection/test_introspection_family_census.py +++ b/tests/rhosocial/activerecord_postgres_test/feature/backend/introspection/test_introspection_family_census.py @@ -40,6 +40,7 @@ from typing import Any, Dict +from rhosocial.activerecord.backend.dialect.exceptions import UnsupportedFeatureError from rhosocial.activerecord.backend.introspection import IntrospectionNotSupportedError @@ -114,7 +115,6 @@ def test_every_family_answers_or_refuses_by_name(postgres_backend): answered = [] refused = defaultdict(list) - skipped = [] server_errors = defaultdict(list) for name in sorted(dir(introspector)): @@ -124,14 +124,15 @@ def test_every_family_answers_or_refuses_by_name(postgres_backend): if not callable(method): continue kwargs = _probe_arguments_for(method) - if kwargs is None: - skipped.append(name) - continue try: method(**kwargs) - except IntrospectionNotSupportedError as error: - # A refusal is an answer: this engine does not serve the family, and - # it says so by name. + except (IntrospectionNotSupportedError, UnsupportedFeatureError) as error: + # A refusal is an answer: the engine or its dialect does not serve + # the family, and it says so by a typed refusal rather than by + # sending a statement the server rejects. Both classes are the + # contract's own vocabulary -- the capability gate is the + # dialect's, the family gate is the introspector's -- so the + # group key keeps them apart without treating either as a defect. refused[_refusal_name(error)].append(name) except Exception as error: # noqa: BLE001 — the census classifies, not swallows # Grouped by (family, first line): a version-skew defect then reads @@ -142,15 +143,6 @@ def test_every_family_answers_or_refuses_by_name(postgres_backend): else: answered.append(name) - # Nothing may be silently un-askable. A parameter this walk cannot - # name a probe value for is a gap in the walk, not a gap in the backend — it is reported - # so the next contract change makes the walk grow rather than skip. - assert not skipped, ( - "these families were not asked because no probe value names their " - "parameters; extend _probe_arguments_for rather than leaving them " - f"unvisited: {sorted(skipped)}" - ) - assert not server_errors, ( "a catalog statement named something this release does not serve. " "A fake executor cannot catch this class of defect — it answers whatever " @@ -173,10 +165,10 @@ def test_argument_synthesis_asks_the_smallest_question(): """The walk is checked without a server, so it cannot rot silently. A signature with a schema, a table, a column, a limit and a name asks five - things; one with a parameter no rule covers is skipped rather than guessed - at — an unknown shape is a question this walk does not know how to ask, and - skipping is honest where guessing could ask a different one than the - contract means. + things; one with a parameter no rule covers keeps that parameter's own + default -- an absence the server can read, never a value invented for it. + Every family is therefore asked: nothing is skipped, so a family that + answers nothing is a fact about the backend rather than about the walk. """ def smallest(schema=None, table_name="", column_name="", limit=0, name=""): pass From 701ec6cd12332ccb845338c45b284c2fa6133c50 Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Thu, 12 Nov 2026 14:29:38 +0800 Subject: [PATCH 4/5] ci: track the released core instead of the feature branch Workflow 'test.yml' installs this backend's dependencies from the other repositories in the family. Core's PR #140 is merged into release/v1.0.0.dev30, so the core ref now names that branch: a run of this repository tests the released core rather than a feature branch that no longer exists. The testsuite ref is unchanged and still names feature/declaration-surface: its migration has no merge yet, and pointing it at release/v1.0.0.dev17 would install fixtures that still declare the removed dunders. --- .check_expression_baseline.json | 284 ++++++++++++++++++++++++++++++++ .github/workflows/test.yml | 8 +- 2 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 .check_expression_baseline.json diff --git a/.check_expression_baseline.json b/.check_expression_baseline.json new file mode 100644 index 00000000..9f1db57a --- /dev/null +++ b/.check_expression_baseline.json @@ -0,0 +1,284 @@ +{ + "entries": { + "003a7d23c21f1da3": 2, + "01a07beafaf8cc3a": 1, + "035fd1d22b0cbaae": 1, + "0a2acd20ea9eb701": 1, + "0d4a842fd919a7e3": 1, + "0ee3bc85a9eb66e2": 1, + "124a30e5d51c1fbc": 1, + "34c9ceaeabea0755": 1, + "42d4fa8eb94c17d3": 2, + "44e86dc8267bde88": 1, + "467f5776c03d8185": 1, + "571c32b05232f854": 1, + "615a793e1f2abc76": 1, + "630bd3782bdfd73e": 1, + "66f45fd04cf19f69": 1, + "73cf3d38f2261311": 1, + "74cebfe91f50b09c": 1, + "79cf8aaa65045a16": 1, + "7b5f861248dcbf83": 1, + "809835b8aab74f60": 1, + "8b832f6ab51443f6": 1, + "911c3c5a980c4b45": 1, + "92e86402b81e1b74": 1, + "993c58144efcfce4": 1, + "9a18e8fab9fbdba9": 1, + "9b9e4544e446c6af": 1, + "a8aae9eb0fb36d80": 1, + "ae8d96891c0683ff": 1, + "af012e4fb6621ad6": 1, + "b298b9ae52803683": 1, + "b77afa3b2a9c6ca7": 2, + "cb759f72d177a5e4": 1, + "cbb90e42db44b249": 1, + "ccb8ae79fccda3ae": 1, + "dc6a74c7f4206aa7": 1, + "e5bdab9e82e9c86c": 1, + "e5ccfd797f0f4ed4": 2, + "e6a593a7d2fe60a4": 1, + "e9b3055c241f2b54": 2, + "eb678d2f80622a98": 1, + "ec043c63eac5e089": 1, + "fd172692ec137a9e": 1 + }, + "findings": [ + { + "found": "is_partitioned: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/postgres/expression/copy.py", + "rule": "ED-10.2" + }, + { + "found": "default_table: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/postgres/expression/ddl/pg_partman.py", + "rule": "ED-10.2" + }, + { + "found": "jobmon: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/postgres/expression/ddl/pg_partman.py", + "rule": "ED-10.2" + }, + { + "found": "infinite_time_partitions: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/postgres/expression/ddl/pg_partman.py", + "rule": "ED-10.2" + }, + { + "found": "retention_keep_table: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/postgres/expression/ddl/pg_partman.py", + "rule": "ED-10.2" + }, + { + "found": "retention_keep_index: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/postgres/expression/ddl/pg_partman.py", + "rule": "ED-10.2" + }, + { + "found": "preferred: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/postgres/expression/ddl/type.py", + "rule": "ED-10.2" + }, + { + "found": "collatable: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/postgres/expression/ddl/type.py", + "rule": "ED-10.2" + }, + { + "found": "process_toast: Optional[bool]", + "path": "src/rhosocial/activerecord/backend/impl/postgres/expression/ddl/vacuum.py", + "rule": "ED-10.2" + }, + { + "found": "getattr(action, \"if_not_exists\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/column.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"if_exists\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/column.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"if_exists\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/column.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"cascade\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/column.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"using\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/column.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action, \"operation\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/column.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr.object_type, \"value\", expr.object_type)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/comment.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"enforced\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/constraint.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"not_enforced\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/constraint.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(action.constraint, \"enforced\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/constraint.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(\n action.constraint, \"not_enforced\", False\n )", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/constraint.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"if_exists\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/domain.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"cascade\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/domain.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"restrict\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/domain.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'action_kind', type(expr).__name__)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/domain.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"opclasses\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/index.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"nulls_not_distinct\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/index.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"with_options\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/index.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"concurrent\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/index.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"cascade\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"restrict\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"cascade\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"restrict\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'definition_kind', type(expr).__name__)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'definition_kind', type(expr).__name__)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'action_kind', type(expr).__name__)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'action_kind', type(expr).__name__)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl/type.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"partition\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"table_options\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"temporary\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(constraint, 'is_auto_increment', False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/ddl_table.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr.on_error, \"value\", expr.on_error)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/dml/copy.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr.log_verbosity, \"value\", expr.log_verbosity)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/dml/copy.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"if_not_exists\", False)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/materialized_view.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, 'action_kind', type(expr).__name__)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/materialized_view.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"mode\", JSONPathMode.AUTO)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/types/jsonb_enhanced.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr_val, 'table', None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/upsert.py", + "rule": "ED-11.2" + }, + { + "found": "getattr(expr, \"alias\", None)", + "path": "src/rhosocial/activerecord/backend/impl/postgres/mixins/uuid.py", + "rule": "ED-11.2" + } + ], + "format": 1 +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24c4b919..8fafd35f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,7 +63,7 @@ jobs: - name: Install activerecord and testsuite run: | - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR - name: Install dependencies @@ -278,7 +278,7 @@ jobs: - name: Install activerecord and testsuite run: | - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR - name: Install dependencies @@ -385,7 +385,7 @@ jobs: - name: Install activerecord and testsuite run: | - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR - name: Install dependencies @@ -488,7 +488,7 @@ jobs: - name: Install activerecord and testsuite run: | - pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR - name: Install dependencies From c040bc384ff93393e6d68ab5a425973f19dd66ba Mon Sep 17 00:00:00 2001 From: zhao vistart Date: Thu, 12 Nov 2026 15:19:55 +0800 Subject: [PATCH 5/5] ci: track the merged testsuite instead of the feature branch The testsuite's PR #32 is merged into release/v1.0.0.dev17, so the testsuite ref now names that branch. Every cross-repository install in a run therefore resolves to a released artifact: core from release/v1.0.0.dev30, the testsuite from release/v1.0.0.dev17, and this repository from its own feature branch. --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8fafd35f..449317f5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,7 +64,7 @@ jobs: - name: Install activerecord and testsuite run: | pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17" # NOSONAR - name: Install dependencies run: | @@ -279,7 +279,7 @@ jobs: - name: Install activerecord and testsuite run: | pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17" # NOSONAR - name: Install dependencies run: | @@ -386,7 +386,7 @@ jobs: - name: Install activerecord and testsuite run: | pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17" # NOSONAR - name: Install dependencies run: | @@ -489,7 +489,7 @@ jobs: - name: Install activerecord and testsuite run: | pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30" # NOSONAR - pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/declaration-surface" # NOSONAR + pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17" # NOSONAR - name: Install dependencies run: |