diff --git a/application/single_app/Dockerfile b/application/single_app/Dockerfile index 7d3d79c9f..99bfff5d2 100644 --- a/application/single_app/Dockerfile +++ b/application/single_app/Dockerfile @@ -38,12 +38,12 @@ RUN set -eux; \ driver_lib="$(find /opt/microsoft/msodbcsql18/lib64 -name 'libmsodbcsql-*.so*' | sort | tail -n 1)"; \ test -n "${driver_lib}"; \ printf '[ODBC Driver 18 for SQL Server]\nDescription=Microsoft ODBC Driver 18 for SQL Server\nDriver=%s\nUsageCount=1\n' "${driver_lib}" > /etc/odbcinst.ini; \ - mkdir -p /odbc-runtime/usr/lib64; \ + mkdir -p /odbc-runtime/usr/lib; \ for lib in /usr/lib64/libodbc* /usr/lib/libodbc* /usr/lib64/libltdl* /usr/lib/libltdl*; do \ - if [ -e "${lib}" ]; then cp -a "${lib}" /odbc-runtime/usr/lib64/; fi; \ + if [ -e "${lib}" ]; then cp -a "${lib}" /odbc-runtime/usr/lib/; fi; \ done; \ - find /odbc-runtime/usr/lib64 -maxdepth 1 -name 'libodbc*' | grep -q .; \ - find /odbc-runtime/usr/lib64 -maxdepth 1 -name 'libltdl*' | grep -q .; \ + find /odbc-runtime/usr/lib -maxdepth 1 -name 'libodbc*' | grep -q .; \ + find /odbc-runtime/usr/lib -maxdepth 1 -name 'libltdl*' | grep -q .; \ tdnf clean all RUN set -eux; \ @@ -129,14 +129,15 @@ RUN set -eux; \ esac; \ driver_config_dir="${driver_config_file%/odbcinst.ini}"; \ test -f "${driver_config_file}"; \ - mkdir -p /odbc-runtime/usr/lib64 /odbc-runtime/opt "/odbc-runtime${driver_config_dir}" /odbc-runtime/etc; \ + mkdir -p /odbc-runtime/usr/lib /odbc-runtime/opt "/odbc-runtime${driver_config_dir}" /odbc-runtime/etc; \ cp -a "${driver_config_file}" "/odbc-runtime${driver_config_dir}/"; \ if [ "${driver_config_dir}" != "/etc" ]; then cp -a "${driver_config_file}" /odbc-runtime/etc/; fi; \ cp -a /opt/microsoft /odbc-runtime/opt/; \ - cp -a /usr/lib64/libodbc.so* /odbc-runtime/usr/lib64/; \ - cp -a /usr/lib64/libodbcinst.so* /odbc-runtime/usr/lib64/; \ - cp -a /usr/lib64/libodbccr.so* /odbc-runtime/usr/lib64/; \ - cp -a /usr/lib64/libltdl.so* /odbc-runtime/usr/lib64/ + for lib in /usr/lib64/libodbc.so* /usr/lib/libodbc.so* /usr/lib64/libodbcinst.so* /usr/lib/libodbcinst.so* /usr/lib64/libodbccr.so* /usr/lib/libodbccr.so* /usr/lib64/libltdl.so* /usr/lib/libltdl.so*; do \ + if [ -e "${lib}" ]; then cp -a "${lib}" /odbc-runtime/usr/lib/; fi; \ + done; \ + find /odbc-runtime/usr/lib -maxdepth 1 -name 'libodbc*' | grep -q .; \ + find /odbc-runtime/usr/lib -maxdepth 1 -name 'libltdl*' | grep -q . WORKDIR /app @@ -161,9 +162,11 @@ RUN set -eux; \ true|1|yes|on) \ python3 -m playwright install chromium; \ find /ms-playwright -name chrome_sandbox -exec chown root:root {} \; -exec chmod 4755 {} \;; \ - mkdir -p /playwright-runtime/ms-playwright /playwright-runtime/usr/lib64 /playwright-runtime/usr/share /playwright-runtime/etc; \ + mkdir -p /playwright-runtime/ms-playwright /playwright-runtime/usr/lib /playwright-runtime/usr/share /playwright-runtime/etc; \ cp -a /ms-playwright/. /playwright-runtime/ms-playwright/; \ - cp -a /usr/lib64/*.so* /playwright-runtime/usr/lib64/; \ + for lib in /usr/lib64/*.so* /usr/lib/*.so*; do \ + if [ -e "${lib}" ]; then cp -a "${lib}" /playwright-runtime/usr/lib/; fi; \ + done; \ if [ -d /usr/share/fonts ]; then cp -a /usr/share/fonts /playwright-runtime/usr/share/; fi; \ if [ -d /usr/share/fontconfig ]; then cp -a /usr/share/fontconfig /playwright-runtime/usr/share/; fi; \ if [ -d /etc/fonts ]; then cp -a /etc/fonts /playwright-runtime/etc/; fi; \ diff --git a/application/single_app/config.py b/application/single_app/config.py index 4b44e54ca..a6bb61a3a 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -97,7 +97,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.261.007" +VERSION = "0.261.009" IS_DEVELOPMENT = is_development_env_enabled() # Opt-out for deployments where App Service Easy Auth is active but the platform diff --git a/application/single_app/functions_document_access_index.py b/application/single_app/functions_document_access_index.py index fa53e9c05..fb2b1605f 100644 --- a/application/single_app/functions_document_access_index.py +++ b/application/single_app/functions_document_access_index.py @@ -2165,7 +2165,7 @@ def _query_candidate_projection_rows_for_scope(scope_key, source_scope): 'WHERE c.type = @type ' 'AND c.source_scope = @source_scope ' 'AND c.scope_key = @scope_key ' - 'AND c.access_granted = true ' + 'AND (c.access_granted = true OR c.approval_status = @approval_not_approved) ' 'AND c.is_current_version = true ' 'AND c.projection_version = @projection_version' ) @@ -2177,6 +2177,7 @@ def _query_candidate_projection_rows_for_scope(scope_key, source_scope): {'name': '@type', 'value': DOCUMENT_ACCESS_INDEX_TYPE}, {'name': '@source_scope', 'value': source_scope}, {'name': '@scope_key', 'value': scope_key}, + {'name': '@approval_not_approved', 'value': DOCUMENT_ACCESS_APPROVAL_NOT_APPROVED}, {'name': '@projection_version', 'value': DOCUMENT_ACCESS_INDEX_SCHEMA_VERSION}, ], partition_key=scope_key, diff --git a/docs/explanation/fixes/DISTROLESS_RUNTIME_OVERLAY_PATH_FIX.md b/docs/explanation/fixes/DISTROLESS_RUNTIME_OVERLAY_PATH_FIX.md new file mode 100644 index 000000000..7b1dc699a --- /dev/null +++ b/docs/explanation/fixes/DISTROLESS_RUNTIME_OVERLAY_PATH_FIX.md @@ -0,0 +1,54 @@ +# Distroless Runtime Overlay Path Fix + +Fixed in version: **0.261.009** + +Related config.py version update: `application/single_app/config.py` was incremented to `0.261.009`. + +## Issue Description + +Container builds could fail during final-stage runtime overlay copy with: + +`cannot copy to non-directory ... /usr/lib64` + +The failure was first observed on: + +- `COPY --from=builder /odbc-runtime/ /` + +After addressing that path, the same failure surfaced on: + +- `COPY --from=builder /playwright-runtime/ /` + +## Root Cause Analysis + +The builder staged runtime payloads under directory trees containing `usr/lib64`, then copied those trees into `/` on a distroless base image. On newer base-image layouts, `/usr/lib64` may be a symlink or other non-directory entry, and Docker BuildKit refuses to overlay a directory on top of a non-directory path. + +This made full-tree overlays brittle against base image filesystem changes. + +## Technical Details + +Files modified: + +- `application/single_app/Dockerfile` +- `application/single_app/config.py` +- `functional_tests/test_sql_container_odbc_runtime.py` +- `functional_tests/test_sql_odbc_driver_18_support.py` + +Code changes summary: + +- Updated ODBC staging destination from `"/odbc-runtime/usr/lib64"` to `"/odbc-runtime/usr/lib"`. +- Updated Playwright staging destination from `"/playwright-runtime/usr/lib64"` to `"/playwright-runtime/usr/lib"`. +- Preserved source library glob coverage from both `/usr/lib64` and `/usr/lib` in the builder stage so either package layout remains supported. +- Kept final runtime copies (`/odbc-runtime/` and `/playwright-runtime/`) unchanged while eliminating directory-vs-non-directory path collisions at `/usr/lib64`. + +## Validation + +Validation evidence for the change sequence included: + +- `functional_tests/test_sql_container_odbc_runtime.py` passed after assertion alignment and Dockerfile path updates. +- `functional_tests/test_sql_odbc_driver_18_support.py` continued to validate SQL ODBC Driver 18 runtime/default behavior. +- `functional_tests/test_deep_research_chromium_build_opt_out.py` covered Playwright Chromium build-arg and runtime copy wiring. + +## Before and After + +- Before: BuildKit could fail in the final stage when runtime overlay trees attempted to create `usr/lib64` as a directory over a non-directory target in the distroless base. +- After: Runtime overlays stage libraries under `usr/lib`, avoiding `/usr/lib64` target-shape conflicts while preserving SQL ODBC and Playwright native runtime packaging. \ No newline at end of file diff --git a/docs/explanation/fixes/WORKSPACE_SHARED_FILE_APPROVAL_VISIBILITY_FIX.md b/docs/explanation/fixes/WORKSPACE_SHARED_FILE_APPROVAL_VISIBILITY_FIX.md new file mode 100644 index 000000000..a1b184b74 --- /dev/null +++ b/docs/explanation/fixes/WORKSPACE_SHARED_FILE_APPROVAL_VISIBILITY_FIX.md @@ -0,0 +1,59 @@ +# Workspace Shared File Approval Visibility Fix + +Fixed in version: **0.261.009** + +Related config.py version update: `application/single_app/config.py` was incremented to `0.261.009`. + +## Issue Description + +Files shared into personal or group workspace approval flows could fail to appear in approval-facing views even when they were waiting for approval. + +The impact was that approvers could not reliably see pending files to review, which blocked the expected approve or deny workflow. + +## Root Cause Analysis + +The document access index scope projection candidate query filtered strictly on granted access: + +- `c.access_granted = true` + +Pending approval records are expected to remain ungranted until an approver acts. Those records carry a pending approval state, so the strict granted-only filter excluded them from candidate projection rows. + +## Technical Details + +Files modified: + +- `application/single_app/functions_document_access_index.py` + +Code changes summary: + +- Updated `_query_candidate_projection_rows_for_scope` to include pending approval rows in addition to granted rows. +- Added the `@approval_not_approved` query parameter bound to `DOCUMENT_ACCESS_APPROVAL_NOT_APPROVED`. + +Updated query predicate: + +- From: `AND c.access_granted = true` +- To: `AND (c.access_granted = true OR c.approval_status = @approval_not_approved)` + +This preserves existing constraints for source scope, scope key, current-version records, and projection schema version while restoring visibility for pending approval artifacts. + +## Validation + +Validation performed for this change set included: + +- Python compile checks for changed files and `application/single_app` compile sweep. +- Security guardrails for changed Python files: + - `scripts/check_xss_sinks.py` + - `scripts/check_broken_access_control.py` + +Additional branch validation: + +- Updated and re-ran `functional_tests/test_sql_container_odbc_runtime.py` to align assertions with current SQL ODBC default behavior (3/3 passed). + +Recommended follow-up coverage: + +- Add a dedicated functional test that asserts pending approval records are included in document access index scope projections for approver-visible queues. + +## Before and After + +- Before: Pending approval shared files could be missing from approval visibility surfaces because only granted access rows were projected. +- After: Pending approval shared files are included in projection candidates and remain visible to approvers while still ungranted until approval. \ No newline at end of file diff --git a/docs/explanation/fixes/index.md b/docs/explanation/fixes/index.md index 5557504c1..294c896ce 100644 --- a/docs/explanation/fixes/index.md +++ b/docs/explanation/fixes/index.md @@ -7,6 +7,8 @@ category: Version History --- - [Chat Document Search File Name and Divider Artifact Fix](CHAT_DOCUMENT_SEARCH_FILENAME_AND_DIVIDER_FIX.md) +- [Distroless Runtime Overlay Path Fix](DISTROLESS_RUNTIME_OVERLAY_PATH_FIX.md) +- [Workspace Shared File Approval Visibility Fix](WORKSPACE_SHARED_FILE_APPROVAL_VISIBILITY_FIX.md) - [Semantic Kernel Startup Request Context Fix](SEMANTIC_KERNEL_STARTUP_REQUEST_CONTEXT_FIX.md) - [Data Management Restore Route Endpoint Collision Fix](DATA_MANAGEMENT_RESTORE_ROUTE_ENDPOINT_COLLISION_FIX.md) - [Font Size and 200 Percent Zoom Fix](FONT_SIZE_AND_200_PERCENT_ZOOM_FIX.md) diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index f3f2b74d2..bcbde2f12 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -2,6 +2,22 @@ For feature-focused and fix-focused drill-downs by version, see [Features by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/features) and [Fixes by Version](https://github.com/microsoft/simplechat/tree/main/docs/explanation/fixes). +### **(v0.261.009)** + +#### Bug Fixes + +* **Shared Workspace File Approvals Are Visible To Approvers Again** + * Fixed document access index candidate selection for workspace scope projections so pending-approval records are considered alongside already granted records. + * Shared files staged for approval are intentionally not granted yet, so filtering only on `access_granted = true` could hide those files from approval experiences even though they were eligible for review. + * The projection query now includes `approval_status = not_approved` rows while still requiring current-version projection records. + * (Ref: `functions_document_access_index.py`, [Workspace Shared File Approval Visibility Fix](fixes/WORKSPACE_SHARED_FILE_APPROVAL_VISIBILITY_FIX.md)) + +* **Distroless Runtime Copy No Longer Fails On `/usr/lib64` Overlay Conflicts** + * Fixed Docker BuildKit failures where `COPY --from=builder /odbc-runtime/ /` or `COPY --from=builder /playwright-runtime/ /` could abort with `cannot copy to non-directory ... /usr/lib64` when the distroless base exposes `/usr/lib64` as a non-directory entry. + * Updated runtime staging to copy native shared libraries into `/odbc-runtime/usr/lib` and `/playwright-runtime/usr/lib` while continuing to source candidates from both `/usr/lib64` and `/usr/lib` in the builder stage. + * This preserves SQL ODBC and Playwright Chromium runtime packaging while avoiding path-type collisions against evolving base-image filesystem layouts. + * (Ref: `Dockerfile`, `test_sql_container_odbc_runtime.py`, `test_deep_research_chromium_build_opt_out.py`, [Distroless Runtime Overlay Path Fix](fixes/DISTROLESS_RUNTIME_OVERLAY_PATH_FIX.md)) + ### **(v0.261.007)** #### Bug Fixes diff --git a/functional_tests/test_sql_container_odbc_runtime.py b/functional_tests/test_sql_container_odbc_runtime.py index 2b80169b5..897ad9fb4 100644 --- a/functional_tests/test_sql_container_odbc_runtime.py +++ b/functional_tests/test_sql_container_odbc_runtime.py @@ -30,6 +30,7 @@ def test_dockerfile_packages_odbc_runtime() -> bool: "tdnf install -y unixODBC unixODBC-devel msodbcsql18", "COPY --from=builder /odbc-runtime/ /", 'LD_LIBRARY_PATH="/usr/lib64:/opt/microsoft/msodbcsql18/lib64"', + "mkdir -p /odbc-runtime/usr/lib /odbc-runtime/opt", 'driver_config_path="$(odbcinst -j | while IFS= read -r line; do case "$line" in DRIVERS*) printf \'%s\\n\' "${line##*: }"; break ;; esac; done)"', 'test -n "${driver_config_path}"', 'case "${driver_config_path}" in', @@ -39,6 +40,8 @@ def test_dockerfile_packages_odbc_runtime() -> bool: 'test -f "${driver_config_file}"', 'cp -a "${driver_config_file}" "/odbc-runtime${driver_config_dir}/"', 'if [ "${driver_config_dir}" != "/etc" ]; then cp -a "${driver_config_file}" /odbc-runtime/etc/; fi;', + 'for lib in /usr/lib64/libodbc.so* /usr/lib/libodbc.so* /usr/lib64/libodbcinst.so* /usr/lib/libodbcinst.so* /usr/lib64/libodbccr.so* /usr/lib/libodbccr.so* /usr/lib64/libltdl.so* /usr/lib/libltdl.so*; do', + 'if [ -e "${lib}" ]; then cp -a "${lib}" /odbc-runtime/usr/lib/; fi;', ] missing = [snippet for snippet in expected_snippets if snippet not in dockerfile] @@ -59,10 +62,10 @@ def test_sql_defaults_use_odbc_driver_18() -> bool: print("🔍 Testing SQL defaults use ODBC Driver 18...") expected_defaults = { - "application/single_app/route_backend_plugins.py": "driver or 'ODBC Driver 18 for SQL Server'", - "application/single_app/semantic_kernel_plugins/sql_query_plugin.py": "'default_driver': 'ODBC Driver 18 for SQL Server'", - "application/single_app/semantic_kernel_plugins/sql_schema_plugin.py": "'default_driver': 'ODBC Driver 18 for SQL Server'", - "application/single_app/semantic_kernel_plugins/sql_plugin_factory.py": '"driver": "ODBC Driver 18 for SQL Server"', + "application/single_app/route_backend_plugins.py": "driver=driver or DEFAULT_SQL_SERVER_ODBC_DRIVER", + "application/single_app/semantic_kernel_plugins/sql_query_plugin.py": "'default_driver': DEFAULT_SQL_SERVER_ODBC_DRIVER", + "application/single_app/semantic_kernel_plugins/sql_schema_plugin.py": "'default_driver': DEFAULT_SQL_SERVER_ODBC_DRIVER", + "application/single_app/semantic_kernel_plugins/sql_plugin_factory.py": '"driver": DEFAULT_SQL_SERVER_ODBC_DRIVER', "application/single_app/static/js/plugin_modal_stepper.js": "additionalFields.driver || 'ODBC Driver 18 for SQL Server'", "application/single_app/semantic_kernel_plugins/SQL_Plugins_Configuration_Guide.md": 'DRIVER={ODBC Driver 18 for SQL Server}', } diff --git a/functional_tests/test_sql_odbc_driver_18_support.py b/functional_tests/test_sql_odbc_driver_18_support.py index 7ba719424..3cd036a3d 100644 --- a/functional_tests/test_sql_odbc_driver_18_support.py +++ b/functional_tests/test_sql_odbc_driver_18_support.py @@ -43,7 +43,7 @@ def test_dockerfile_installs_driver_18_runtime(): assert "find /opt/microsoft/msodbcsql18/lib64 -name 'libmsodbcsql-*.so*'" in dockerfile, \ 'Docker build should resolve the installed Driver 18 shared library path' assert "UsageCount=1" in dockerfile, 'Docker build should create a deterministic odbcinst.ini registration' - assert '/odbc-runtime/usr/lib64' in dockerfile, \ + assert '/odbc-runtime/usr/lib' in dockerfile, \ 'Docker build should stage unixODBC libraries from Azure Linux library paths before runtime copy' assert "/usr/lib64/libodbc* /usr/lib/libodbc*" in dockerfile, \ 'Docker build should support unixODBC libraries installed under /usr/lib64 or /usr/lib'